refs/files: perform consistency checks for root refs

While the "files" backend already knows to perform consistency checks for the "refs/" hierarchy, it doesn't verify any of its root refs. Plug this omission. 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 7b8c36a2a74868b6fa47e3b11e2c1c0f89c88d43
2 files changed +77 -3
refs/files-backend.c
+47 -3
@@ -3877,9 +3877,9 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
3877 if (filename[0] != '.' && ends_with(filename, ".lock"))
3878 goto cleanup;
3879
3880 - /*
3881 - * This works right now because we never check the root refs.
3882 - */
3880 + if (is_root_ref(refname))
3881 + goto cleanup;
3882 +
3883 if (check_refname_format(refname, 0)) {
3884 struct fsck_ref_report report = { 0 };
3885
@@ -3974,19 +3974,63 @@ out:
3974 return ret;
3975 }
3976
3977 +struct files_fsck_root_ref_data {
3978 + struct files_ref_store *refs;
3979 + struct fsck_options *o;
3980 + struct worktree *wt;
3981 + struct strbuf refname;
3982 + struct strbuf path;
3983 +};
3984 +
3985 +static int files_fsck_root_ref(const char *refname, void *cb_data)
3986 +{
3987 + struct files_fsck_root_ref_data *data = cb_data;
3988 + struct stat st;
3989 +
3990 + strbuf_reset(&data->refname);
3991 + if (!is_main_worktree(data->wt))
3992 + strbuf_addf(&data->refname, "worktrees/%s/", data->wt->id);
3993 + strbuf_addstr(&data->refname, refname);
3994 +
3995 + strbuf_reset(&data->path);
3996 + strbuf_addf(&data->path, "%s/%s", data->refs->gitcommondir, data->refname.buf);
3997 +
3998 + if (stat(data->path.buf, &st)) {
3999 + if (errno == ENOENT)
4000 + return 0;
4001 + return error_errno("failed to read ref: '%s'", data->path.buf);
4002 + }
4003 +
4004 + return files_fsck_ref(&data->refs->base, data->o, data->refname.buf,
4005 + data->path.buf, st.st_mode);
4006 +}
4007 +
4008 static int files_fsck(struct ref_store *ref_store,
4009 struct fsck_options *o,
4010 struct worktree *wt)
4011 {
4012 struct files_ref_store *refs =
4013 files_downcast(ref_store, REF_STORE_READ, "fsck");
4014 + struct files_fsck_root_ref_data data = {
4015 + .refs = refs,
4016 + .o = o,
4017 + .wt = wt,
4018 + .refname = STRBUF_INIT,
4019 + .path = STRBUF_INIT,
4020 + };
4021 int ret = 0;
4022
4023 if (files_fsck_refs_dir(ref_store, o, wt) < 0)
4024 ret = -1;
4025 +
4026 + if (for_each_root_ref(refs, files_fsck_root_ref, &data) < 0)
4027 + ret = -1;
4028 +
4029 if (refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt) < 0)
4030 ret = -1;
4031
4032 + strbuf_release(&data.refname);
4033 + strbuf_release(&data.path);
4034 return ret;
4035 }
4036
t/t0602-reffiles-fsck.sh
+30
@@ -905,4 +905,34 @@ test_expect_success '--[no-]references option should apply to fsck' '
905 )
906 '
907
908 +test_expect_success 'complains about broken root ref' '
909 + test_when_finished "rm -rf repo" &&
910 + git init repo &&
911 + (
912 + cd repo &&
913 + echo "ref: refs/../HEAD" >.git/HEAD &&
914 + test_must_fail git refs verify 2>err &&
915 + cat >expect <<-EOF &&
916 + error: HEAD: badReferentName: points to invalid refname ${SQ}refs/../HEAD${SQ}
917 + EOF
918 + test_cmp expect err
919 + )
920 +'
921 +
922 +test_expect_success 'complains about broken root ref in worktree' '
923 + test_when_finished "rm -rf repo worktree" &&
924 + git init repo &&
925 + (
926 + cd repo &&
927 + test_commit initial &&
928 + git worktree add ../worktree &&
929 + echo "ref: refs/../HEAD" >.git/worktrees/worktree/HEAD &&
930 + test_must_fail git refs verify 2>err &&
931 + cat >expect <<-EOF &&
932 + error: worktrees/worktree/HEAD: badReferentName: points to invalid refname ${SQ}refs/../HEAD${SQ}
933 + EOF
934 + test_cmp expect err
935 + )
936 +'
937 +
938 test_done