refs/reftable: introduce generic checks for refs

In a preceding commit we have extracted generic checks for both direct and symbolic refs that apply for all backends. Wire up those checks for the "reftable" backend. Note that this is done by iterating through all refs manually with the low-level reftable ref iterator. We explicitly don't want to use the higher-level iterator that is exposed to users of the reftable backend as that iterator may swallow for example broken refs. 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 06d6ead762ba525c5837812e7f509406253cdacd
2 files changed +88 -6
refs/reftable-backend.c
+76 -6
@@ -2767,19 +2767,89 @@ static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o,
2767 {
2768 struct reftable_ref_store *refs =
2769 reftable_be_downcast(ref_store, REF_STORE_READ, "fsck");
2770 + struct reftable_ref_iterator *iter = NULL;
2771 + struct reftable_ref_record ref = { 0 };
2772 + struct fsck_ref_report report = { 0 };
2773 + struct strbuf refname = STRBUF_INIT;
2774 struct reftable_backend *backend;
2775 + int ret, errors = 0;
2776
2777 if (is_main_worktree(wt)) {
2778 backend = &refs->main_backend;
2779 } 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);
2780 + ret = backend_for_worktree(&backend, refs, wt->id);
2781 + if (ret < 0) {
2782 + ret = error(_("reftable stack for worktree '%s' is broken"),
2783 + wt->id);
2784 + goto out;
2785 + }
2786 + }
2787 +
2788 + errors |= reftable_fsck_check(backend->stack, reftable_fsck_error_handler,
2789 + reftable_fsck_verbose_handler, o);
2790 +
2791 + iter = ref_iterator_for_stack(refs, backend->stack, "", NULL, 0);
2792 + if (!iter) {
2793 + ret = error(_("could not create iterator for worktree '%s'"), wt->id);
2794 + goto out;
2795 + }
2796 +
2797 + while (1) {
2798 + ret = reftable_iterator_next_ref(&iter->iter, &ref);
2799 + if (ret > 0)
2800 + break;
2801 + if (ret < 0) {
2802 + ret = error(_("could not read record for worktree '%s'"), wt->id);
2803 + goto out;
2804 + }
2805 +
2806 + strbuf_reset(&refname);
2807 + if (!is_main_worktree(wt))
2808 + strbuf_addf(&refname, "worktrees/%s/", wt->id);
2809 + strbuf_addstr(&refname, ref.refname);
2810 + report.path = refname.buf;
2811 +
2812 + switch (ref.value_type) {
2813 + case REFTABLE_REF_VAL1:
2814 + case REFTABLE_REF_VAL2: {
2815 + struct object_id oid;
2816 + unsigned hash_id;
2817 +
2818 + switch (reftable_stack_hash_id(backend->stack)) {
2819 + case REFTABLE_HASH_SHA1:
2820 + hash_id = GIT_HASH_SHA1;
2821 + break;
2822 + case REFTABLE_HASH_SHA256:
2823 + hash_id = GIT_HASH_SHA256;
2824 + break;
2825 + default:
2826 + BUG("unhandled hash ID %d",
2827 + reftable_stack_hash_id(backend->stack));
2828 + }
2829 +
2830 + oidread(&oid, reftable_ref_record_val1(&ref),
2831 + &hash_algos[hash_id]);
2832 +
2833 + errors |= refs_fsck_ref(ref_store, o, &report, ref.refname, &oid);
2834 + break;
2835 + }
2836 + case REFTABLE_REF_SYMREF:
2837 + errors |= refs_fsck_symref(ref_store, o, &report, ref.refname,
2838 + ref.value.symref);
2839 + break;
2840 + default:
2841 + BUG("unhandled reference value type %d", ref.value_type);
2842 + }
2843 }
2844
2781 - return reftable_fsck_check(backend->stack, reftable_fsck_error_handler,
2782 - reftable_fsck_verbose_handler, o);
2845 + ret = errors ? -1 : 0;
2846 +
2847 +out:
2848 + if (iter)
2849 + ref_iterator_free(&iter->base);
2850 + reftable_ref_record_release(&ref);
2851 + strbuf_release(&refname);
2852 + return ret;
2853 }
2854
2855 struct ref_storage_be refs_be_reftable = {
t/t0614-reftable-fsck.sh
+12
@@ -87,4 +87,16 @@ test_expect_success 'worktree stacks can be verified' '
87 done
88 '
89
90 +test_expect_success 'invalid symref gets reported' '
91 + test_when_finished "rm -rf repo" &&
92 + git init repo &&
93 + test_commit -C repo initial &&
94 + git -C repo symbolic-ref refs/heads/symref garbage &&
95 + test_must_fail git -C repo refs verify 2>err &&
96 + cat >expect <<-EOF &&
97 + error: refs/heads/symref: badReferentName: points to invalid refname ${SQ}garbage${SQ}
98 + EOF
99 + test_cmp expect err
100 +'
101 +
102 test_done