refs/files: extract generic symref target checks

The consistency checks for the "files" backend contain a couple of verifications for symrefs that verify generic properties of the target reference. These properties need to hold for every backend, no matter whether it's using the "files" or "reftable" backend. Reimplementing these checks for every single backend doesn't really make sense. Extract it into a generic `refs_fsck_symref()` function that can be used by other backends, as well. The "reftable" backend will be wired up in a subsequent commit. While at it, improve the consistency checks so that we don't complain about refs pointing to a non-ref target in case the target refname format does not verify. Otherwise it's very likely that we'll generate both error messages, which feels somewhat redundant in this case. Note that the function has a couple of `UNUSED` parameters. These will become referenced in a subsequent commit. 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:02 UTC dcecffb616762893c44f98d556493144edbcd498
3 files changed +52 -33
refs.c
+21
@@ -320,6 +320,27 @@ int check_refname_format(const char *refname, int flags)
320 return check_or_sanitize_refname(refname, flags, NULL);
321 }
322
323 +int refs_fsck_symref(struct ref_store *refs UNUSED, struct fsck_options *o,
324 + struct fsck_ref_report *report,
325 + const char *refname UNUSED, const char *target)
326 +{
327 + if (is_root_ref(target))
328 + return 0;
329 +
330 + if (check_refname_format(target, 0) &&
331 + fsck_report_ref(o, report, FSCK_MSG_BAD_REFERENT_NAME,
332 + "points to invalid refname '%s'", target))
333 + return -1;
334 +
335 + if (!starts_with(target, "refs/") &&
336 + !starts_with(target, "worktrees/") &&
337 + fsck_report_ref(o, report, FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF,
338 + "points to non-ref target '%s'", target))
339 + return -1;
340 +
341 + return 0;
342 +}
343 +
344 int refs_fsck(struct ref_store *refs, struct fsck_options *o,
345 struct worktree *wt)
346 {
refs.h
+10
@@ -653,6 +653,16 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat
653 */
654 int check_refname_format(const char *refname, int flags);
655
656 +struct fsck_ref_report;
657 +
658 +/*
659 + * Perform generic checks for a specific symref target. This function is
660 + * expected to be called by the ref backends for every symbolic ref.
661 + */
662 +int refs_fsck_symref(struct ref_store *refs, struct fsck_options *o,
663 + struct fsck_ref_report *report,
664 + const char *refname, const char *target);
665 +
666 /*
667 * Check the reference database for consistency. Return 0 if refs and
668 * reflogs are consistent, and non-zero otherwise. The errors will be
refs/files-backend.c
+21 -33
@@ -3718,53 +3718,39 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
3718 const char *path,
3719 int mode);
3720
3721 -static int files_fsck_symref_target(struct fsck_options *o,
3721 +static int files_fsck_symref_target(struct ref_store *ref_store,
3722 + struct fsck_options *o,
3723 struct fsck_ref_report *report,
3724 + const char *refname,
3725 struct strbuf *referent,
3726 unsigned int symbolic_link)
3727 {
3726 - int is_referent_root;
3728 char orig_last_byte;
3729 size_t orig_len;
3730 int ret = 0;
3731
3732 orig_len = referent->len;
3733 orig_last_byte = referent->buf[orig_len - 1];
3733 - if (!symbolic_link)
3734 - strbuf_rtrim(referent);
3735 -
3736 - is_referent_root = is_root_ref(referent->buf);
3737 - if (!is_referent_root &&
3738 - !starts_with(referent->buf, "refs/") &&
3739 - !starts_with(referent->buf, "worktrees/")) {
3740 - ret |= fsck_report_ref(o, report,
3741 - FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF,
3742 - "points to non-ref target '%s'", referent->buf);
3743 - }
3734
3745 - if (!is_referent_root && check_refname_format(referent->buf, 0)) {
3746 - ret |= fsck_report_ref(o, report,
3747 - FSCK_MSG_BAD_REFERENT_NAME,
3748 - "points to invalid refname '%s'", referent->buf);
3749 - }
3735 + if (!symbolic_link) {
3736 + strbuf_rtrim(referent);
3737
3751 - if (symbolic_link)
3752 - goto out;
3738 + if (referent->len == orig_len ||
3739 + (referent->len < orig_len && orig_last_byte != '\n')) {
3740 + ret |= fsck_report_ref(o, report,
3741 + FSCK_MSG_REF_MISSING_NEWLINE,
3742 + "misses LF at the end");
3743 + }
3744
3754 - if (referent->len == orig_len ||
3755 - (referent->len < orig_len && orig_last_byte != '\n')) {
3756 - ret |= fsck_report_ref(o, report,
3757 - FSCK_MSG_REF_MISSING_NEWLINE,
3758 - "misses LF at the end");
3745 + if (referent->len != orig_len && referent->len != orig_len - 1) {
3746 + ret |= fsck_report_ref(o, report,
3747 + FSCK_MSG_TRAILING_REF_CONTENT,
3748 + "has trailing whitespaces or newlines");
3749 + }
3750 }
3751
3761 - if (referent->len != orig_len && referent->len != orig_len - 1) {
3762 - ret |= fsck_report_ref(o, report,
3763 - FSCK_MSG_TRAILING_REF_CONTENT,
3764 - "has trailing whitespaces or newlines");
3765 - }
3752 + ret |= refs_fsck_symref(ref_store, o, report, refname, referent->buf);
3753
3767 -out:
3754 return ret ? -1 : 0;
3755 }
3756
@@ -3807,7 +3793,8 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
3793 else
3794 strbuf_addbuf(&referent, &ref_content);
3795
3810 - ret |= files_fsck_symref_target(o, &report, &referent, 1);
3796 + ret |= files_fsck_symref_target(ref_store, o, &report,
3797 + target_name, &referent, 1);
3798 goto cleanup;
3799 }
3800
@@ -3847,7 +3834,8 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
3834 goto cleanup;
3835 }
3836 } else {
3850 - ret = files_fsck_symref_target(o, &report, &referent, 0);
3837 + ret = files_fsck_symref_target(ref_store, o, &report,
3838 + target_name, &referent, 0);
3839 goto cleanup;
3840 }
3841