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>

Patrick Steinhardt committed Jan 12, 2026 at 10:03 UTC 9341740bea2ce334be412835fdcc0dd31550071a
2 files changed +46 -15
refs/reftable-backend.c
+14 -15
@@ -26,6 +26,7 @@
26 #include "../setup.h"
27 #include "../strmap.h"
28 #include "../trace2.h"
29 +#include "../worktree.h"
30 #include "../write-or-die.h"
31 #include "refs-internal.h"
32
@@ -2762,25 +2763,23 @@ static int reftable_fsck_error_handler(struct reftable_fsck_info *info,
2763 }
2764
2765 static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
2765 - struct worktree *wt UNUSED)
2766 + struct worktree *wt)
2767 {
2767 - struct reftable_ref_store *refs;
2768 - struct strmap_entry *entry;
2769 - struct hashmap_iter iter;
2770 - int ret = 0;
2771 -
2772 - refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
2773 -
2774 - ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler,
2775 - reftable_fsck_verbose_handler, o);
2768 + struct reftable_ref_store *refs =
2769 + reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
2770 + struct reftable_backend *backend;
2771
2777 - strmap_for_each_entry(&refs->worktree_backends, &iter, entry) {
2778 - struct reftable_backend *b = (struct reftable_backend *)entry->value;
2779 - ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler,
2780 - reftable_fsck_verbose_handler, o);
2772 + if (is_main_worktree(wt)) {
2773 + backend = &refs->main_backend;
2774 + } else {
2775 + int ret = backend_for_worktree(&backend, refs, wt->id);
2776 + if (ret < 0)
2777 + return error(_("reftable stack for worktree '%s' is broken"),
2778 + wt->id);
2779 }
2780
2783 - return ret;
2781 + return reftable_fsck_check(backend->stack, reftable_fsck_error_handler,
2782 + reftable_fsck_verbose_handler, o);
2783 }
2784
2785 struct ref_storage_be refs_be_reftable = {
t/t0614-reftable-fsck.sh
+32
@@ -55,4 +55,36 @@ for TABLE_NAME in "foo-bar-e4d12d59.ref" \
55 '
56 done
57
58 +test_expect_success 'worktree stacks can be verified' '
59 + test_when_finished "rm -rf repo worktree" &&
60 + git init repo &&
61 + test_commit -C repo initial &&
62 + git -C repo worktree add ../worktree &&
63 +
64 + git -C worktree refs verify 2>err &&
65 + test_must_be_empty err &&
66 +
67 + REFTABLE_DIR=$(git -C worktree rev-parse --git-dir)/reftable &&
68 + EXISTING_TABLE=$(head -n1 "$REFTABLE_DIR/tables.list") &&
69 + mv "$REFTABLE_DIR/$EXISTING_TABLE" "$REFTABLE_DIR/broken.ref" &&
70 +
71 + for d in repo worktree
72 + do
73 + echo "broken.ref" >"$REFTABLE_DIR/tables.list" &&
74 + git -C "$d" refs verify 2>err &&
75 + cat >expect <<-EOF &&
76 + warning: broken.ref: badReftableTableName: invalid reftable table name
77 + EOF
78 + test_cmp expect err &&
79 +
80 + echo garbage >"$REFTABLE_DIR/tables.list" &&
81 + test_must_fail git -C "$d" refs verify 2>err &&
82 + cat >expect <<-EOF &&
83 + error: reftable stack for worktree ${SQ}worktree${SQ} is broken
84 + EOF
85 + test_cmp expect err || return 1
86 +
87 + done
88 +'
89 +
90 test_done