diff mbox series

[v5,3/9] ref: port git-fsck(1) regular refs check for files backend

Message ID Zvj-osCNDMrUQv83@ArchLinux (mailing list archive)
State New
Headers show
Series add ref content check for files backend | expand

Commit Message

shejialuo Sept. 29, 2024, 7:15 a.m. UTC
"git-fsck(1)" has some consistency checks for regular refs. As we want
to align the checks "git refs verify" performs with them (and eventually
call the unified code that checks refs from both), port the logic
"git-fsck" has to "git refs verify".

"git-fsck(1)" will report an error when the ref content is invalid.
Following this, add a similar check to "git refs verify". Then add a new
fsck error message "badRefContent(ERROR)" to represent that a ref has an
invalid content.

Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: shejialuo <shejialuo@gmail.com>
---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 45 ++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 66 +++++++++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+)

Comments

Patrick Steinhardt Oct. 7, 2024, 6:58 a.m. UTC | #1
On Sun, Sep 29, 2024 at 03:15:46PM +0800, shejialuo wrote:
> "git-fsck(1)" has some consistency checks for regular refs. As we want
> to align the checks "git refs verify" performs with them (and eventually
> call the unified code that checks refs from both), port the logic
> "git-fsck" has to "git refs verify".

What's missing here is the actual intent of this commit, namely why we
want to align the checks. I assume that this prepares us for calling
`git refs verify` as part of git-fsck(1), but readers not familiar with
the larger picture may be left wondering.

> "git-fsck(1)" will report an error when the ref content is invalid.
> Following this, add a similar check to "git refs verify". Then add a new
> fsck error message "badRefContent(ERROR)" to represent that a ref has an
> invalid content.

It would help readers to know where the code is that you're porting over
to `git refs verify` so that one can double check that the port is done
faithfully to the original.

Patrick
shejialuo Oct. 7, 2024, 8:42 a.m. UTC | #2
On Mon, Oct 07, 2024 at 08:58:34AM +0200, Patrick Steinhardt wrote:
> On Sun, Sep 29, 2024 at 03:15:46PM +0800, shejialuo wrote:
> > "git-fsck(1)" has some consistency checks for regular refs. As we want
> > to align the checks "git refs verify" performs with them (and eventually
> > call the unified code that checks refs from both), port the logic
> > "git-fsck" has to "git refs verify".
> 
> What's missing here is the actual intent of this commit, namely why we
> want to align the checks. I assume that this prepares us for calling
> `git refs verify` as part of git-fsck(1), but readers not familiar with
> the larger picture may be left wondering.
> 

Indeed, I will improve this in the next version.

> > "git-fsck(1)" will report an error when the ref content is invalid.
> > Following this, add a similar check to "git refs verify". Then add a new
> > fsck error message "badRefContent(ERROR)" to represent that a ref has an
> > invalid content.
> 
> It would help readers to know where the code is that you're porting over
> to `git refs verify` so that one can double check that the port is done
> faithfully to the original.
> 

I am a little confused here. There are too many codes in "git-fsck(1)"
to check the ref consistency. How could I accurately express this info
in the commit message?

> Patrick

Thanks,
Jialuo
Patrick Steinhardt Oct. 7, 2024, 9:18 a.m. UTC | #3
On Mon, Oct 07, 2024 at 04:42:44PM +0800, shejialuo wrote:
> On Mon, Oct 07, 2024 at 08:58:34AM +0200, Patrick Steinhardt wrote:
> > On Sun, Sep 29, 2024 at 03:15:46PM +0800, shejialuo wrote:
> > > "git-fsck(1)" will report an error when the ref content is invalid.
> > > Following this, add a similar check to "git refs verify". Then add a new
> > > fsck error message "badRefContent(ERROR)" to represent that a ref has an
> > > invalid content.
> > 
> > It would help readers to know where the code is that you're porting over
> > to `git refs verify` so that one can double check that the port is done
> > faithfully to the original.
> > 
> 
> I am a little confused here. There are too many codes in "git-fsck(1)"
> to check the ref consistency. How could I accurately express this info
> in the commit message?

Well, you say you ported over a specific consistency check from
git-fsck(1) to `git refs verify` in the commit message. So I assume that
it should match a specific check in git-fsck(1), shouldn't it?

Patrick
shejialuo Oct. 7, 2024, 12:08 p.m. UTC | #4
On Mon, Oct 07, 2024 at 11:18:24AM +0200, Patrick Steinhardt wrote:
> On Mon, Oct 07, 2024 at 04:42:44PM +0800, shejialuo wrote:
> > On Mon, Oct 07, 2024 at 08:58:34AM +0200, Patrick Steinhardt wrote:
> > > On Sun, Sep 29, 2024 at 03:15:46PM +0800, shejialuo wrote:
> > > > "git-fsck(1)" will report an error when the ref content is invalid.
> > > > Following this, add a similar check to "git refs verify". Then add a new
> > > > fsck error message "badRefContent(ERROR)" to represent that a ref has an
> > > > invalid content.
> > > 
> > > It would help readers to know where the code is that you're porting over
> > > to `git refs verify` so that one can double check that the port is done
> > > faithfully to the original.
> > > 
> > 
> > I am a little confused here. There are too many codes in "git-fsck(1)"
> > to check the ref consistency. How could I accurately express this info
> > in the commit message?
> 
> Well, you say you ported over a specific consistency check from
> git-fsck(1) to `git refs verify` in the commit message. So I assume that
> it should match a specific check in git-fsck(1), shouldn't it?
> 

I understand your meaning here. I will improve the commit message in the
next version.

> Patrick
Karthik Nayak Oct. 8, 2024, 7:43 a.m. UTC | #5
shejialuo <shejialuo@gmail.com> writes:

[snip]

> +	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
> +		ret = fsck_report_ref(o, &report,
> +				      FSCK_MSG_BAD_REF_CONTENT,
> +				      "cannot read ref file");
> +		goto cleanup;
> +	}
> +

Shouldn't we use `die_errno` here instead? I mean, this is not really a
bad ref content issue. If we don't want to die here, it would still
probably be nice to get the actual issue using `strerror` instead and
use that instead of the generic message we have here.

[snip]
shejialuo Oct. 8, 2024, 12:24 p.m. UTC | #6
On Tue, Oct 08, 2024 at 12:43:20AM -0700, Karthik Nayak wrote:
> shejialuo <shejialuo@gmail.com> writes:
> 
> [snip]
> 
> > +	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
> > +		ret = fsck_report_ref(o, &report,
> > +				      FSCK_MSG_BAD_REF_CONTENT,
> > +				      "cannot read ref file");
> > +		goto cleanup;
> > +	}
> > +
> 
> Shouldn't we use `die_errno` here instead? I mean, this is not really a
> bad ref content issue. If we don't want to die here, it would still
> probably be nice to get the actual issue using `strerror` instead and
> use that instead of the generic message we have here.
> 

Well, I think I need to dive into the "open" system call here. Actually,
we have two opinions now. Junio thought that we should use
"fsck_report_ref" to report. Karthik, Patrick and I thought that we
should report using "*errno" because this is a general error.

Let me investigate what situations will make "open" system call fail.
Thus, we could fully explain which choice we will choose.

Thanks,
Jialuo
Junio C Hamano Oct. 8, 2024, 5:44 p.m. UTC | #7
shejialuo <shejialuo@gmail.com> writes:

> On Tue, Oct 08, 2024 at 12:43:20AM -0700, Karthik Nayak wrote:
>> shejialuo <shejialuo@gmail.com> writes:
>> 
>> [snip]
>> 
>> > +	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
>> > +		ret = fsck_report_ref(o, &report,
>> > +				      FSCK_MSG_BAD_REF_CONTENT,
>> > +				      "cannot read ref file");
>> > +		goto cleanup;
>> > +	}
>> > +
>> 
>> Shouldn't we use `die_errno` here instead? I mean, this is not really a
>> bad ref content issue. If we don't want to die here, it would still
>> probably be nice to get the actual issue using `strerror` instead and
>> use that instead of the generic message we have here.
>> 
>
> Well, I think I need to dive into the "open" system call here. Actually,
> we have two opinions now. Junio thought that we should use
> "fsck_report_ref" to report. Karthik, Patrick and I thought that we
> should report using "*errno" because this is a general error.

What do you mean by "a general error"?  It is true that we failed to
read a ref file, so even if it is an I/O error, I'd think it is OK
to report it as an error while reading one particular ref.

Giving more information is a separate issue.  If fsck_report_ref()
can be extended to take something like

    "cannot read ref file '%s': (%s)", iter->path.buf, strerror(errno)

that would give the user necessary information.

And I agree with half-of what Karthik said, i.e., we do not want to
die here if this is meant to run as a part of "git fsck".

I may have said this before, but quite frankly, the API into the
fsck_report_ref() function is misdesigned.  If the single constant
string "cannot read ref file" cnanot give more information than
FSCK_MSG_BAD_REF_CONTENT, the parameter has no value.

The fsck.c:report() function, which is the main function to report
fsck's findings before fsck_report_ref() was introduced, did not
have such a problem, as it allowed "const char *fmt, ..." at the
end.  Is it too late to fix the fsck_report_ref()?

Thanks.
Patrick Steinhardt Oct. 9, 2024, 8:05 a.m. UTC | #8
On Tue, Oct 08, 2024 at 10:44:53AM -0700, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
> 
> > On Tue, Oct 08, 2024 at 12:43:20AM -0700, Karthik Nayak wrote:
> >> shejialuo <shejialuo@gmail.com> writes:
> >> 
> >> [snip]
> >> 
> >> > +	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
> >> > +		ret = fsck_report_ref(o, &report,
> >> > +				      FSCK_MSG_BAD_REF_CONTENT,
> >> > +				      "cannot read ref file");
> >> > +		goto cleanup;
> >> > +	}
> >> > +
> >> 
> >> Shouldn't we use `die_errno` here instead? I mean, this is not really a
> >> bad ref content issue. If we don't want to die here, it would still
> >> probably be nice to get the actual issue using `strerror` instead and
> >> use that instead of the generic message we have here.
> >> 
> >
> > Well, I think I need to dive into the "open" system call here. Actually,
> > we have two opinions now. Junio thought that we should use
> > "fsck_report_ref" to report. Karthik, Patrick and I thought that we
> > should report using "*errno" because this is a general error.
> 
> What do you mean by "a general error"?  It is true that we failed to
> read a ref file, so even if it is an I/O error, I'd think it is OK
> to report it as an error while reading one particular ref.
> 
> Giving more information is a separate issue.  If fsck_report_ref()
> can be extended to take something like
> 
>     "cannot read ref file '%s': (%s)", iter->path.buf, strerror(errno)
> 
> that would give the user necessary information.

Yeah, this is also in line with what I proposed elsewhere, where we have
been discussing the 1:1 mapping between error codes and error messages.
If the error messages were dynamic they'd be a whole lot useful overall
and could provide more context.

> And I agree with half-of what Karthik said, i.e., we do not want to
> die here if this is meant to run as a part of "git fsck".
> 
> I may have said this before, but quite frankly, the API into the
> fsck_report_ref() function is misdesigned.  If the single constant
> string "cannot read ref file" cnanot give more information than
> FSCK_MSG_BAD_REF_CONTENT, the parameter has no value.

True in the current form, yeah. If `fsck_report_ref()` learned to take a
vararg argument and treat its first argument as a string format it would
be justified though, as the message is now dynamic and can contain more
context around the specific failure that cannot be provided statically
via the 1:1 mapping between error code and message.

> The fsck.c:report() function, which is the main function to report
> fsck's findings before fsck_report_ref() was introduced, did not
> have such a problem, as it allowed "const char *fmt, ..." at the
> end.  Is it too late to fix the fsck_report_ref()?

I don't think so, I think we should be able to refactor the code rather
easily to do so.

Patrick
shejialuo Oct. 9, 2024, 11:55 a.m. UTC | #9
On Tue, Oct 08, 2024 at 10:44:53AM -0700, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
> 
> > On Tue, Oct 08, 2024 at 12:43:20AM -0700, Karthik Nayak wrote:
> >> shejialuo <shejialuo@gmail.com> writes:
> >> 
> >> [snip]
> >> 
> >> > +	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
> >> > +		ret = fsck_report_ref(o, &report,
> >> > +				      FSCK_MSG_BAD_REF_CONTENT,
> >> > +				      "cannot read ref file");
> >> > +		goto cleanup;
> >> > +	}
> >> > +
> >> 
> >> Shouldn't we use `die_errno` here instead? I mean, this is not really a
> >> bad ref content issue. If we don't want to die here, it would still
> >> probably be nice to get the actual issue using `strerror` instead and
> >> use that instead of the generic message we have here.
> >> 
> >
> > Well, I think I need to dive into the "open" system call here. Actually,
> > we have two opinions now. Junio thought that we should use
> > "fsck_report_ref" to report. Karthik, Patrick and I thought that we
> > should report using "*errno" because this is a general error.
> 
> What do you mean by "a general error"?  It is true that we failed to
> read a ref file, so even if it is an I/O error, I'd think it is OK
> to report it as an error while reading one particular ref.

Make sense.

> Giving more information is a separate issue.  If fsck_report_ref()
> can be extended to take something like
> 
>     "cannot read ref file '%s': (%s)", iter->path.buf, strerror(errno)
> 
> that would give the user necessary information.

At current, the `fsck_report_ref` can do this. I think I used
`fsck_report_ref` function badly in this case.

> And I agree with half-of what Karthik said, i.e., we do not want to
> die here if this is meant to run as a part of "git fsck".

Yes, we should not die the program. Instead, we need to continuously
check other refs.

> I may have said this before, but quite frankly, the API into the
> fsck_report_ref() function is misdesigned.  If the single constant
> string "cannot read ref file" cnanot give more information than
> FSCK_MSG_BAD_REF_CONTENT, the parameter has no value.
> 
> The fsck.c:report() function, which is the main function to report
> fsck's findings before fsck_report_ref() was introduced, did not
> have such a problem, as it allowed "const char *fmt, ..." at the
> end.  Is it too late to fix the fsck_report_ref()?

I agree that if the FSCK message id could explain the error well, there
is no need for us to provide extra message. But, I want to say the
`fsck_report_ref` is not misdesigned here. It is just the same as the
"fsck.c::report" function which has "const char *fmt, ..." at the end
like the following shows:

    int fsck_report_ref(struct fsck_options *options,
                        struct fsck_ref_report *report,
                        enum fsck_msg_id msg_id,
                        const char *fmt, ...)

And I do think "fsck.c::report" function also has the above problems.
Let me give you some examples here in "fsck.c":

    report(options, tree_oid, OBJ_TREE,
           FSCK_MSG_BAD_FILEMODE,
           "contains bad file modes");

    report(options, tree_oid, OBJ_TREE,
           FSCK_MSG_DUPLICATE_ENTRIES,
           "contains duplicate file entries");

    ...

So, I want to say there is no difference between "fsck_ref_report" and
"fsck.c::report". When I refactored the code in GSoC journey, the main
problem is that we should reuse the original "fsck.c::report" code
instead of writing redundant codes.

The final result is I extract a new function "fsck_vreport" here (I
leverage the original "fsck.c::report" function) which will be called by
"fsck_ref_report" and "fsck.c::report".

    static int fsck_vreport(struct fsck_options *options,
                            void *fsck_report,
                            enum fsck_msg_id msg_id,
                            const char *fmt, va_list ap)

From my perspective, if we decide to refactor, we should allow the user
call the followings:

    fsck_ref_report(..., FSCK_MSG_BAD_REF_CONTENT, NULL);
    report(..., FSCK_MSG_DUPLICATE_ENTRIES, NULL);

So, we should check whether `fmt` is NULL in the `fsck_vreport`
function to make sure that if FSCK message is good enough to explain
what happens, we should not pass any message.

> Thanks.

Thanks,
Jialuo
shejialuo Oct. 9, 2024, 11:59 a.m. UTC | #10
On Wed, Oct 09, 2024 at 10:05:19AM +0200, Patrick Steinhardt wrote:

[snip]

> > I may have said this before, but quite frankly, the API into the
> > fsck_report_ref() function is misdesigned.  If the single constant
> > string "cannot read ref file" cnanot give more information than
> > FSCK_MSG_BAD_REF_CONTENT, the parameter has no value.
> 
> True in the current form, yeah. If `fsck_report_ref()` learned to take a
> vararg argument and treat its first argument as a string format it would
> be justified though, as the message is now dynamic and can contain more
> context around the specific failure that cannot be provided statically
> via the 1:1 mapping between error code and message.
> 

It is not "learned". At current, `fsck_report_ref` can do this and is
the same as "fsck.c::report". I have explained this when replying to
Junio.

> > The fsck.c:report() function, which is the main function to report
> > fsck's findings before fsck_report_ref() was introduced, did not
> > have such a problem, as it allowed "const char *fmt, ..." at the
> > end.  Is it too late to fix the fsck_report_ref()?
> 
> I don't think so, I think we should be able to refactor the code rather
> easily to do so.
> 

It's not hard to refactor the code. But this is not the problem. I am a
little confused here. Because we already allowed "fsck_report_ref"
having "const char *fmt, ..." at the end.

> Patrick

Thanks,
Jialuo
Patrick Steinhardt Oct. 10, 2024, 6:52 a.m. UTC | #11
On Wed, Oct 09, 2024 at 07:59:20PM +0800, shejialuo wrote:
> On Wed, Oct 09, 2024 at 10:05:19AM +0200, Patrick Steinhardt wrote:
> > > The fsck.c:report() function, which is the main function to report
> > > fsck's findings before fsck_report_ref() was introduced, did not
> > > have such a problem, as it allowed "const char *fmt, ..." at the
> > > end.  Is it too late to fix the fsck_report_ref()?
> > 
> > I don't think so, I think we should be able to refactor the code rather
> > easily to do so.
> > 
> 
> It's not hard to refactor the code. But this is not the problem. I am a
> little confused here. Because we already allowed "fsck_report_ref"
> having "const char *fmt, ..." at the end.

Ah, I didn't double check, but was operating on what I understood from
this thread. In that case I think that the current interface is okay.

Patrick
Junio C Hamano Oct. 10, 2024, 4 p.m. UTC | #12
Patrick Steinhardt <ps@pks.im> writes:

> On Wed, Oct 09, 2024 at 07:59:20PM +0800, shejialuo wrote:
>> On Wed, Oct 09, 2024 at 10:05:19AM +0200, Patrick Steinhardt wrote:
>> > > The fsck.c:report() function, which is the main function to report
>> > > fsck's findings before fsck_report_ref() was introduced, did not
>> > > have such a problem, as it allowed "const char *fmt, ..." at the
>> > > end.  Is it too late to fix the fsck_report_ref()?
>> > 
>> > I don't think so, I think we should be able to refactor the code rather
>> > easily to do so.
>> > 
>> 
>> It's not hard to refactor the code. But this is not the problem. I am a
>> little confused here. Because we already allowed "fsck_report_ref"
>> having "const char *fmt, ..." at the end.
>
> Ah, I didn't double check, but was operating on what I understood from
> this thread. In that case I think that the current interface is okay.

I didn't, either.  So there is an obvious way out for "why aren't we
telling the errno to users" issue?  That's good.
diff mbox series

Patch

diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index 68a2801f15..22c385ea22 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -19,6 +19,9 @@ 
 `badParentSha1`::
 	(ERROR) A commit object has a bad parent sha1.
 
+`badRefContent`::
+	(ERROR) A ref has bad content.
+
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
 
diff --git a/fsck.h b/fsck.h
index 500b4c04d2..0d99a87911 100644
--- a/fsck.h
+++ b/fsck.h
@@ -31,6 +31,7 @@  enum fsck_msg_type {
 	FUNC(BAD_NAME, ERROR) \
 	FUNC(BAD_OBJECT_SHA1, ERROR) \
 	FUNC(BAD_PARENT_SHA1, ERROR) \
+	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 57318b4c4e..35b3fa983e 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3504,6 +3504,50 @@  typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  const char *refs_check_dir,
 				  struct dir_iterator *iter);
 
+static int files_fsck_refs_content(struct ref_store *ref_store,
+				   struct fsck_options *o,
+				   const char *refs_check_dir,
+				   struct dir_iterator *iter)
+{
+	struct strbuf ref_content = STRBUF_INIT;
+	struct strbuf referent = STRBUF_INIT;
+	struct strbuf refname = STRBUF_INIT;
+	struct fsck_ref_report report = { 0 };
+	unsigned int type = 0;
+	int failure_errno = 0;
+	struct object_id oid;
+	int ret = 0;
+
+	strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
+	report.path = refname.buf;
+
+	if (S_ISLNK(iter->st.st_mode))
+		goto cleanup;
+
+	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_BAD_REF_CONTENT,
+				      "cannot read ref file");
+		goto cleanup;
+	}
+
+	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
+				     ref_content.buf, &oid, &referent,
+				     &type, &failure_errno)) {
+		strbuf_rtrim(&ref_content);
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_BAD_REF_CONTENT,
+				      "%s", ref_content.buf);
+		goto cleanup;
+	}
+
+cleanup:
+	strbuf_release(&refname);
+	strbuf_release(&ref_content);
+	strbuf_release(&referent);
+	return ret;
+}
+
 static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 				struct fsck_options *o,
 				const char *refs_check_dir,
@@ -3586,6 +3630,7 @@  static int files_fsck_refs(struct ref_store *ref_store,
 {
 	files_fsck_refs_fn fsck_refs_fn[]= {
 		files_fsck_refs_name,
+		files_fsck_refs_content,
 		NULL,
 	};
 
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
index 4c6cd6f7d0..628f9bcc46 100755
--- a/t/t0602-reffiles-fsck.sh
+++ b/t/t0602-reffiles-fsck.sh
@@ -148,4 +148,70 @@  test_expect_success 'ref name check should work for multiple worktrees' '
 	)
 '
 
+test_expect_success 'regular ref content should be checked (individual)' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	branch_dir_prefix=.git/refs/heads &&
+	tag_dir_prefix=.git/refs/tags &&
+	cd repo &&
+	test_commit default &&
+	mkdir -p "$branch_dir_prefix/a/b" &&
+
+	git refs verify 2>err &&
+	test_must_be_empty err &&
+
+	bad_content=$(git rev-parse main)x &&
+	printf "%s" $bad_content >$tag_dir_prefix/tag-bad-1 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/tags/tag-bad-1: badRefContent: $bad_content
+	EOF
+	rm $tag_dir_prefix/tag-bad-1 &&
+	test_cmp expect err &&
+
+	bad_content=xfsazqfxcadas &&
+	printf "%s" $bad_content >$tag_dir_prefix/tag-bad-2 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/tags/tag-bad-2: badRefContent: $bad_content
+	EOF
+	rm $tag_dir_prefix/tag-bad-2 &&
+	test_cmp expect err &&
+
+	bad_content=Xfsazqfxcadas &&
+	printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/heads/a/b/branch-bad: badRefContent: $bad_content
+	EOF
+	rm $branch_dir_prefix/a/b/branch-bad &&
+	test_cmp expect err
+'
+
+test_expect_success 'regular ref content should be checked (aggregate)' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	branch_dir_prefix=.git/refs/heads &&
+	tag_dir_prefix=.git/refs/tags &&
+	cd repo &&
+	test_commit default &&
+	mkdir -p "$branch_dir_prefix/a/b" &&
+
+	bad_content_1=$(git rev-parse main)x &&
+	bad_content_2=xfsazqfxcadas &&
+	bad_content_3=Xfsazqfxcadas &&
+	printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 &&
+	printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 &&
+	printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad &&
+
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3
+	error: refs/tags/tag-bad-1: badRefContent: $bad_content_1
+	error: refs/tags/tag-bad-2: badRefContent: $bad_content_2
+	EOF
+	sort err >sorted_err &&
+	test_cmp expect sorted_err
+'
+
 test_done