refs/reftable: fix consistency checks with worktrees

The ref consistency checks are driven via `cmd_refs_verify()`. That
function loops through all worktrees (including the main worktree) and
then checks the ref store for each of them individually. It follows that
the backend is expected to only verify refs that belong to the specified
worktree.

While the "files" backend handles this correctly, the "reftable" backend
doesn't. In fact, it completely ignores the passed worktree and instead
verifies refs of _all_ worktrees. The consequence is that we'll end up
every ref store N times, where N is the number of worktrees.

Or rather, that would be the case if we actually iterated through the
worktree reftable stacks correctly. But we use `strmap_for_each_entry()`
to iterate through the stacks, but the map is in fact not even properly
populated. So instead of checking stacks N^2 times, we actually only end
up checking the reftable stack of the main worktree.

Fix this bug by only verifying the stack of the passed-in worktree and
constructing the backends via `backend_for_worktree()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Patrick Steinhardt 2026-01-12 10:03:02 +01:00 committed by Junio C Hamano
parent 78384e2467
commit 9341740bea
2 changed files with 46 additions and 15 deletions

View File

@ -26,6 +26,7 @@
#include "../setup.h"
#include "../strmap.h"
#include "../trace2.h"
#include "../worktree.h"
#include "../write-or-die.h"
#include "refs-internal.h"
@ -2762,25 +2763,23 @@ static int reftable_fsck_error_handler(struct reftable_fsck_info *info,
}
static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
struct worktree *wt UNUSED)
struct worktree *wt)
{
struct reftable_ref_store *refs;
struct strmap_entry *entry;
struct hashmap_iter iter;
int ret = 0;
struct reftable_ref_store *refs =
reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
struct reftable_backend *backend;
refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler,
reftable_fsck_verbose_handler, o);
strmap_for_each_entry(&refs->worktree_backends, &iter, entry) {
struct reftable_backend *b = (struct reftable_backend *)entry->value;
ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler,
reftable_fsck_verbose_handler, o);
if (is_main_worktree(wt)) {
backend = &refs->main_backend;
} else {
int ret = backend_for_worktree(&backend, refs, wt->id);
if (ret < 0)
return error(_("reftable stack for worktree '%s' is broken"),
wt->id);
}
return ret;
return reftable_fsck_check(backend->stack, reftable_fsck_error_handler,
reftable_fsck_verbose_handler, o);
}
struct ref_storage_be refs_be_reftable = {

View File

@ -55,4 +55,36 @@ for TABLE_NAME in "foo-bar-e4d12d59.ref" \
'
done
test_expect_success 'worktree stacks can be verified' '
test_when_finished "rm -rf repo worktree" &&
git init repo &&
test_commit -C repo initial &&
git -C repo worktree add ../worktree &&
git -C worktree refs verify 2>err &&
test_must_be_empty err &&
REFTABLE_DIR=$(git -C worktree rev-parse --git-dir)/reftable &&
EXISTING_TABLE=$(head -n1 "$REFTABLE_DIR/tables.list") &&
mv "$REFTABLE_DIR/$EXISTING_TABLE" "$REFTABLE_DIR/broken.ref" &&
for d in repo worktree
do
echo "broken.ref" >"$REFTABLE_DIR/tables.list" &&
git -C "$d" refs verify 2>err &&
cat >expect <<-EOF &&
warning: broken.ref: badReftableTableName: invalid reftable table name
EOF
test_cmp expect err &&
echo garbage >"$REFTABLE_DIR/tables.list" &&
test_must_fail git -C "$d" refs verify 2>err &&
cat >expect <<-EOF &&
error: reftable stack for worktree ${SQ}worktree${SQ} is broken
EOF
test_cmp expect err || return 1
done
'
test_done