builtin/fsck: move generic object ID checks into `refs_fsck()`

While most of the logic that verifies the consistency of refs is driven by `refs_fsck()`, we still have a small handful of checks in `fsck_head_link()`. These checks don't use the git-fsck(1) reporting infrastructure, and as such it's impossible to for example disable some of those checks. One such check detects refs that point to the all-zeroes object ID. Extract this check into the generic `refs_fsck_ref()` function that is used by both the "files" and "reftable" backends. Note that this will cause us to not return an error code from `fsck_head_link()` anymore in case this error was detected. This is fine though: the only caller of this function does not check the error code anyway. To demonstrate this, adapt the function to drop its return value altogether. The function will be removed in a subsequent commit anyway. 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 46d611cadab500ca2b458b2fda7008c41b174011
5 files changed +30 -32
Documentation/fsck-msgids.adoc
+3
@@ -41,6 +41,9 @@
41 `badRefName`::
42 (ERROR) A ref has an invalid format.
43
44 +`badRefOid`::
45 + (ERROR) A ref points to an invalid object ID.
46 +
47 `badReferentName`::
48 (ERROR) The referent name of a symref is invalid.
49
builtin/fsck.c
+15 -26
@@ -564,9 +564,9 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
564 return 0;
565 }
566
567 -static int fsck_head_link(const char *head_ref_name,
568 - const char **head_points_at,
569 - struct object_id *head_oid);
567 +static void fsck_head_link(const char *head_ref_name,
568 + const char **head_points_at,
569 + struct object_id *head_oid);
570
571 static void get_default_heads(void)
572 {
@@ -713,12 +713,10 @@ static void fsck_source(struct odb_source *source)
713 stop_progress(&progress);
714 }
715
716 -static int fsck_head_link(const char *head_ref_name,
717 - const char **head_points_at,
718 - struct object_id *head_oid)
716 +static void fsck_head_link(const char *head_ref_name,
717 + const char **head_points_at,
718 + struct object_id *head_oid)
719 {
720 - int null_is_error = 0;
721 -
720 if (verbose)
721 fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
722
@@ -727,27 +725,18 @@ static int fsck_head_link(const char *head_ref_name,
725 NULL);
726 if (!*head_points_at) {
727 errors_found |= ERROR_REFS;
730 - return error(_("invalid %s"), head_ref_name);
728 + error(_("invalid %s"), head_ref_name);
729 + return;
730 }
732 - if (!strcmp(*head_points_at, head_ref_name))
733 - /* detached HEAD */
734 - null_is_error = 1;
735 - else if (!starts_with(*head_points_at, "refs/heads/")) {
731 + if (strcmp(*head_points_at, head_ref_name) &&
732 + !starts_with(*head_points_at, "refs/heads/")) {
733 errors_found |= ERROR_REFS;
737 - return error(_("%s points to something strange (%s)"),
738 - head_ref_name, *head_points_at);
739 - }
740 - if (is_null_oid(head_oid)) {
741 - if (null_is_error) {
742 - errors_found |= ERROR_REFS;
743 - return error(_("%s: detached HEAD points at nothing"),
744 - head_ref_name);
745 - }
746 - fprintf_ln(stderr,
747 - _("notice: %s points to an unborn branch (%s)"),
748 - head_ref_name, *head_points_at + 11);
734 + error(_("%s points to something strange (%s)"),
735 + head_ref_name, *head_points_at);
736 + return;
737 }
750 - return 0;
738 +
739 + return;
740 }
741
742 static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
fsck.h
+1
@@ -39,6 +39,7 @@ enum fsck_msg_type {
39 FUNC(BAD_REF_CONTENT, ERROR) \
40 FUNC(BAD_REF_FILETYPE, ERROR) \
41 FUNC(BAD_REF_NAME, ERROR) \
42 + FUNC(BAD_REF_OID, ERROR) \
43 FUNC(BAD_TIMEZONE, ERROR) \
44 FUNC(BAD_TREE, ERROR) \
45 FUNC(BAD_TREE_SHA1, ERROR) \
refs.c
+8 -3
@@ -320,10 +320,15 @@ int check_refname_format(const char *refname, int flags)
320 return check_or_sanitize_refname(refname, flags, NULL);
321 }
322
323 -int refs_fsck_ref(struct ref_store *refs UNUSED, struct fsck_options *o UNUSED,
324 - struct fsck_ref_report *report UNUSED,
325 - const char *refname UNUSED, const struct object_id *oid UNUSED)
323 +int refs_fsck_ref(struct ref_store *refs UNUSED, struct fsck_options *o,
324 + struct fsck_ref_report *report,
325 + const char *refname UNUSED, const struct object_id *oid)
326 {
327 + if (is_null_oid(oid))
328 + return fsck_report_ref(o, report, FSCK_MSG_BAD_REF_OID,
329 + "points to invalid object ID '%s'",
330 + oid_to_hex(oid));
331 +
332 return 0;
333 }
334
t/t1450-fsck.sh
+3 -3
@@ -105,7 +105,7 @@ test_expect_success REFFILES 'HEAD link pointing at a funny object' '
105 echo $ZERO_OID >.git/HEAD &&
106 # avoid corrupt/broken HEAD from interfering with repo discovery
107 test_must_fail env GIT_DIR=.git git fsck 2>out &&
108 - test_grep "detached HEAD points" out
108 + test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
109 '
110
111 test_expect_success 'HEAD link pointing at a funny place' '
@@ -123,7 +123,7 @@ test_expect_success REFFILES 'HEAD link pointing at a funny object (from differe
123 echo $ZERO_OID >.git/HEAD &&
124 # avoid corrupt/broken HEAD from interfering with repo discovery
125 test_must_fail git -C wt fsck 2>out &&
126 - test_grep "main-worktree/HEAD: detached HEAD points" out
126 + test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
127 '
128
129 test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' '
@@ -131,7 +131,7 @@ test_expect_success REFFILES 'other worktree HEAD link pointing at a funny objec
131 git worktree add other &&
132 echo $ZERO_OID >.git/worktrees/other/HEAD &&
133 test_must_fail git fsck 2>out &&
134 - test_grep "worktrees/other/HEAD: detached HEAD points" out
134 + test_grep "worktrees/other/HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out
135 '
136
137 test_expect_success 'other worktree HEAD link pointing at missing object' '