Hi,
Deleting root refs in the files backend unnecessarily locks packed-refs, even though root refs cannot be packed. This can cause post-commit cleanup to report an error after a successful commit in a linked worktree with read-only shared metadata.
The attached patch skips that lock for root-ref deletion and adds regression tests. All seven new tests fail without the fix and pass with it; broader ref, worktree, and sequencer tests also pass.
AI assistance was used to generate the patch, tests, and commit message.
Thanks, Ariel
From c5d12e97a78123965590553fddc0dd78af3e006e Mon Sep 17 00:00:00 2001 From: Ariel Keselman <skariel@gmail.com> Date: Wed, 9 Sep 2026 22:16:50 -0700 To: git@vger.kernel.org Subject: [PATCH] refs/files: avoid packed-refs lock for root ref deletion
Deleting a root ref queues a packed-ref transaction in the files backend, even though root refs cannot be packed. For example, holding .git/packed-refs.lock makes "git update-ref --no-deref -d AUTO_MERGE" fail, whether or not AUTO_MERGE exists.
This also affects post-commit cleanup, which deletes AUTO_MERGE after updating HEAD. In a linked worktree with read-only shared metadata, commit succeeds but cleanup reports a packed-refs.lock error. Deleting CHERRY_PICK_HEAD and REVERT_HEAD is affected as well.
Skip the packed transaction for root-ref deletions. Keep loose-ref locking and packed-ref deletion for other refs unchanged.
Test existing and absent root refs with packed-refs.lock held. Also check that root-ref deletion leaves packed refs intact, and that a packed branch still requires the lock and can be deleted once it is released.
Signed-off-by: Ariel Keselman <skariel@gmail.com> --- AI assistance was used to generate the patch, tests, and commit message.
Based on maint at e9019fcafe (Git 2.55).
Validation: - All seven new tests fail with packed-refs.lock errors without the fix. - With the fix, t0600 and t0601 pass (one platform skip in t0600). - Broader ref, worktree, rebase, cherry-pick, commit and merge tests pass: 123 scripts, 4083 tests on the maint-based tree. - After merging with master at b8242b093d, 128 scripts / 4178 tests and 260 unit tests pass.
refs/files-backend.c | 9 +++++--- t/t0600-reffiles-backend.sh | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-)
Show changes to 2 files +52 −3
refs/files-backend.c, t/t0600-reffiles-backend.sh
diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787..41887f180f 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2981,10 +2981,13 @@ static int files_transaction_prepare(struct ref_store *ref_store, if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY) && - !(update->flags & REF_IS_PRUNING)) { + !(update->flags & REF_IS_PRUNING) && + !is_root_ref(update->refname)) { /* - * This reference has to be deleted from - * packed-refs if it exists there. + * Root refs cannot be packed. Do not acquire the shared + * packed-refs lock when deleting a per-worktree root ref. + * Other references have to be deleted from + * packed-refs if they exist there. */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index 74bfa2e9ba..b7f3287841 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -519,4 +519,50 @@ test_expect_success 'symref transaction supports false symlink config' ' test_cmp expect actual ' +for ref in AUTO_MERGE CHERRY_PICK_HEAD REVERT_HEAD +do + for state in existing missing + do + test_expect_success "deleting $state $ref does not lock packed-refs" ' + test_when_finished "rm -rf root-ref" && + git init root-ref && + ( + cd root-ref && + test_commit initial && + if test "$state" = existing + then + git update-ref "$ref" HEAD + fi && + : >.git/packed-refs.lock && + git -c core.packedRefsTimeout=0 update-ref --no-deref -d "$ref" && + test_path_is_missing ".git/$ref" && + test_path_is_file .git/packed-refs.lock + ) + ' + done +done + +test_expect_success 'root ref deletion preserves packed refs and their locking' ' + test_when_finished "rm -rf root-ref" && + git init root-ref && + ( + cd root-ref && + test_commit initial && + git update-ref refs/heads/packed-branch HEAD && + git pack-refs --all && + test_path_is_missing .git/refs/heads/packed-branch && + cp .git/packed-refs expect && + git update-ref AUTO_MERGE HEAD && + : >.git/packed-refs.lock && + git -c core.packedRefsTimeout=0 update-ref --no-deref -d AUTO_MERGE && + test_cmp expect .git/packed-refs && + test_must_fail git -c core.packedRefsTimeout=0 update-ref -d refs/heads/packed-branch 2>err && + test_grep "Unable to create .*packed-refs.lock" err && + test_cmp expect .git/packed-refs && + rm .git/packed-refs.lock && + git update-ref -d refs/heads/packed-branch && + test_must_fail git rev-parse --verify refs/heads/packed-branch + ) +' + test_done
-- 2.55.0