Skip to contentSkip to conversation

Implement ref content consistency check

RFC Superseded shejialuo 6 participants 209 messages Aug 13, 2024 – Nov 20, 2024 Documentation Page 1 of 2

lore
Share & tools
lore

shejialuo

author
#0 lore raw

Hi All:

We have already set up the infrastructure of the ref consistency. However, we have only add ref name check when establishing the infrastructure in below:

https://lore.kernel.org/git/[email protected]/

Actually, we already have a patch here which has already implemented the ref content consistency check. But during the review process, we have encountered some problems. The intention of this RFC is to make sure what content we should check and also to what extend.

I conclude the following info:

1. For the regular ref which has a trailing garbage, we should warn the user. This is the most simplest situation, we could reply on "parse_loose_ref_content" to do this.
2. For the symref, we could also rely on "parse_loose_ref_content" to get the "pointee", and check the location of the "pointee", check the name of the "pointee" and the file type of the "pointee".
3. FOr the symbolic ref, we could follow the idea of 2.

But Patrick gives a question here:

Quoted 5 lines

In case the ref ends with a newline, should we check that the next character is \0? Otherwise, it may contain multiple lines, which is not allowed for a normal ref.

Also, shouldn't the ref always end with a newline?

For symref, I guess we have no spec here. From my experiments, a symref could have a newline or no newline, even multiple newlines. And also symref could have multiple spaces. But the following is a bad symref

ref: refs/heads/main garbage

I think we should fully discuss what we should check here. Thus I will implement the code.

Thanks,
Jialuo

#1 lore raw
Quoted 15 lines · shejialuo

Hi All:

We have already set up the infrastructure of the ref consistency. However, we have only add ref name check when establishing the infrastructure in below:

https://lore.kernel.org/git/[email protected]/

Actually, we already have a patch here which has already implemented the ref content consistency check. But during the review process, we have encountered some problems. The intention of this RFC is to make sure what content we should check and also to what extend.

I conclude the following info:

Quoted 8 lines

1. For the regular ref which has a trailing garbage, we should warn the user. This is the most simplest situation, we could reply on "parse_loose_ref_content" to do this.
2. For the symref, we could also rely on "parse_loose_ref_content" to get the "pointee", and check the location of the "pointee", check the name of the "pointee" and the file type of the "pointee".
3. FOr the symbolic ref, we could follow the idea of 2.

Just to understand clearly, when you're talking about 'symbolic ref' you are referring to symbolic links?

I ask because, as per our documentation in
'Documentation/git-symbolic-ref.txt':

In the past, .git/HEAD was a symbolic link pointing at refs/heads/master. When we wanted to switch to another branch, we did ln -sf refs/heads/newbranch .git/HEAD, and when we wanted to find out which branch we are on, we did readlink .git/HEAD. But symbolic links are not entirely portable, so they are now deprecated and symbolic refs (as described above) are used by default.

Quoted 8 lines

But Patrick gives a question here:

Quoted 5 lines

In case the ref ends with a newline, should we check that the next character is \0? Otherwise, it may contain multiple lines, which is not allowed for a normal ref.

Also, shouldn't the ref always end with a newline?

Quoted 9 lines

For symref, I guess we have no spec here. From my experiments, a symref could have a newline or no newline, even multiple newlines. And also symref could have multiple spaces. But the following is a bad symref

ref: refs/heads/main garbage

I think we should fully discuss what we should check here. Thus I will implement the code.

Agreed, in refs/files-backend.c:create_symref_lock, we write symrefs as "ref: %s\n" so it makes sense to validate that there is nothing extra.

author
#2 lore raw
Quoted 26 lines · karthik nayak
Quoted 15 lines · shejialuo

Hi All:

We have already set up the infrastructure of the ref consistency. However, we have only add ref name check when establishing the infrastructure in below:

https://lore.kernel.org/git/[email protected]/

Actually, we already have a patch here which has already implemented the ref content consistency check. But during the review process, we have encountered some problems. The intention of this RFC is to make sure what content we should check and also to what extend.

I conclude the following info:

Quoted 8 lines

1. For the regular ref which has a trailing garbage, we should warn the user. This is the most simplest situation, we could reply on "parse_loose_ref_content" to do this.
2. For the symref, we could also rely on "parse_loose_ref_content" to get the "pointee", and check the location of the "pointee", check the name of the "pointee" and the file type of the "pointee".
3. FOr the symbolic ref, we could follow the idea of 2.

Quoted 3 lines

Just to understand clearly, when you're talking about 'symbolic ref' you are referring to symbolic links?

I am sorry about this. It's symbolic links here.

Quoted 12 lines
Quoted 12 lines

But Patrick gives a question here:

Quoted 5 lines

In case the ref ends with a newline, should we check that the next character is \0? Otherwise, it may contain multiple lines, which is not allowed for a normal ref.

Also, shouldn't the ref always end with a newline?

For symref, I guess we have no spec here. From my experiments, a symref could have a newline or no newline, even multiple newlines. And also symref could have multiple spaces. But the following is a bad symref

Quoted 8 lines
Quoted 5 lines

ref: refs/heads/main garbage

I think we should fully discuss what we should check here. Thus I will implement the code.

Agreed, in refs/files-backend.c:create_symref_lock, we write symrefs as "ref: %s\n" so it makes sense to validate that there is nothing extra.

Yes, we should do this. I will implement the code and the send the patches to the mailing list.

Thanks

#3 lore raw
Quoted 27 lines · shejialuo
Quoted 26 lines · karthik nayak
Quoted 23 lines · shejialuo

Hi All:

We have already set up the infrastructure of the ref consistency. However, we have only add ref name check when establishing the infrastructure in below:

https://lore.kernel.org/git/[email protected]/

Actually, we already have a patch here which has already implemented the ref content consistency check. But during the review process, we have encountered some problems. The intention of this RFC is to make sure what content we should check and also to what extend.

I conclude the following info:

1. For the regular ref which has a trailing garbage, we should warn the user. This is the most simplest situation, we could reply on "parse_loose_ref_content" to do this.
2. For the symref, we could also rely on "parse_loose_ref_content" to get the "pointee", and check the location of the "pointee", check the name of the "pointee" and the file type of the "pointee".
3. FOr the symbolic ref, we could follow the idea of 2.

Quoted 5 lines
Quoted 3 lines

Just to understand clearly, when you're talking about 'symbolic ref' you are referring to symbolic links?

I am sorry about this. It's symbolic links here.

Wait, is it really symbolic link? I don't think so, you actually were talking about symbolic refs correctly. The fact that symbolic refs have been implemented as a symbolic link in the past (and still can be used for that purpose) is rather an implementation detail. But the overall context, and what we actually want to check on disk, is a symbolic ref in its modern incarnation.

And checking the format of both normal and symbolic refs does make sense in my opinion.

Quoted 18 lines
Quoted 18 lines
Quoted 17 lines

But Patrick gives a question here:

Quoted 5 lines

In case the ref ends with a newline, should we check that the next character is \0? Otherwise, it may contain multiple lines, which is not allowed for a normal ref.

Also, shouldn't the ref always end with a newline?

For symref, I guess we have no spec here. From my experiments, a symref could have a newline or no newline, even multiple newlines. And also symref could have multiple spaces. But the following is a bad symref

ref: refs/heads/main garbage

I think we should fully discuss what we should check here. Thus I will implement the code.

Quoted 5 lines
Quoted 2 lines

Agreed, in refs/files-backend.c:create_symref_lock, we write symrefs as "ref: %s\n" so it makes sense to validate that there is nothing extra.

Yes, we should do this. I will implement the code and the send the patches to the mailing list.

Agreed. We have to exclude pseudorefs (FETCH_HEAD, MERGE_HEAD, see gitglossary(7)) from these checks, as those _are_ allowed to contain extra data. But no other reference should carry more data than that. Namely, a regular ref should always be "hex * hash_len + \n", while a symbolic ref should always be "ref: $valid_refname\n".

A ref that does not conform to this is not a properly formatted reference and thus worth being warned about.

Patrick

#4 lore raw
Quoted 8 lines · Patrick Steinhardt
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

1. For the regular ref which has a trailing garbage, we should warn the user. This is the most simplest situation, we could reply on "parse_loose_ref_content" to do this.
2. For the symref, we could also rely on "parse_loose_ref_content" to get the "pointee", and check the location of the "pointee", check the name of the "pointee" and the file type of the "pointee".
3. FOr the symbolic ref, we could follow the idea of 2.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Just to understand clearly, when you're talking about 'symbolic ref' you are referring to symbolic links?

I am sorry about this. It's symbolic links here.

Wait, is it really symbolic link? I don't think so, you actually were talking about symbolic refs correctly.

In #2, yes. I think #3 is about what to do with a random symbolic link inside or near .git/refs/ hierarchy, which may or may not meant as a symref. I agree that we should assume that the user meant them to be used as symrefs, check its validity the same way as a textual symrefs, and complain if they look bogus.

Quoted 2 lines

And checking the format of both normal and symbolic refs does make sense in my opinion.

Yup.

author
#5 lore raw

cover add ref content check for files backend

In reply to shejialuo

Hi All:

This patch aims at adding ref content check for files backend. By the RFC we have discussed, I add three types of checks.

1. Check regular ref content. I enhance the "parse_loose_ref_contents" to validate the content of the regular ref and warn the user about the trailing garbage.
2. Check symbolic ref content. Check the trailing garbage and content.
3. Check symlink ref by reusing the function introduced by #2.

The CI is passed:

https://github.com/shejialuo/git/pull/14

Thanks,
Jialuo

shejialuo (4):
fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro
ref: add regular ref content check for files backend
ref: add symbolic ref content check for files backend
ref: add symlink ref consistency check for files backend

 Documentation/fsck-msgids.txt |  12 +++
 fsck.h                        |  10 ++
 refs.c                        |   2 +-
 refs/files-backend.c          | 188 +++++++++++++++++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 183 +++++++++++++++++++++++++++++++++
 6 files changed, 392 insertions(+), 5 deletions(-)
Signature · 1 line
2.46.0
author
#6 lore raw

PATCH 1/4 fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

In order to conveniently create a new "fsck_ref_report", add a new macro "FSCK_REF_REPORT_DEFAULT".

---
 fsck.h               | 6 ++++++
 refs/files-backend.c | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)
fsck.h +6 -0
index 500b4c04d2..8894394d16 100644
@@ -152,6 +152,12 @@ struct fsck_ref_report {
 	const char *referent;
 };
 
+#define FSCK_REF_REPORT_DEFAULT { \
+	.path = NULL, \
+	.oid = NULL, \
+	.referent = NULL, \
+}
+
 struct fsck_options {
 	fsck_walk_func walk;
 	fsck_error error_func;
refs/files-backend.c +1 -1
index 8d6ec9458d..725a4f52e3 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.46.0
#7 lore raw

Re: [PATCH v1 1/4] fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro

Quoted 4 lines · shejialuo

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The above is correct, but ...

refs/files-backend.c static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, · line 3449

Quoted 3 lines
refs/files-backend.c +1 -1
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;

... the code without this patch is already doing so.

When designated initializers are used to initialize a struct, all members that are not initialized explicitly are implicitly initialized the same as for objects that have static storage duration (meaning: pointers are initialized to NULL, arithmetics are initialized to zero).

So I do not quite see why this change is needed. By hiding the fact that the "report" structure is zero-initialized behind the macro, it makes it less obvious that we are clearing everything.

If the patch were to rewrite the above like so:

		struct fsck_ref_report report = { 0 }

it would make it even more clear that everything is zero initialized, and also makes it obvious that .path member is not any special.

Thanks.

author
#8 lore raw

Re: [PATCH v1 1/4] fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro

Quoted 16 lines · Junio C Hamano

refs/files-backend.c static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, · line 3449

Quoted 3 lines
refs/files-backend.c +1 -1
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;

... the code without this patch is already doing so.

When designated initializers are used to initialize a struct, all members that are not initialized explicitly are implicitly initialized the same as for objects that have static storage duration (meaning: pointers are initialized to NULL, arithmetics are initialized to zero).

So I do not quite see why this change is needed. By hiding the fact that the "report" structure is zero-initialized behind the macro, it makes it less obvious that we are clearing everything.

Quoted 8 lines

If the patch were to rewrite the above like so:

		struct fsck_ref_report report = { 0 }

it would make it even more clear that everything is zero initialized, and also makes it obvious that .path member is not any special.

Yes, I should use this way. Thanks.

Quoted 1 line

Thanks.

author
#9 lore raw

PATCH 2/4 ref: add regular ref content check for files backend

In reply to shejialuo

We implicitly reply on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend, we allow the ref content to end with no newline or contain some garbages. We should warn the user about above situations.

In order to provide above functionality, enhance the "git-refs verify" command by adding consistency check for regular refs for files backend.

Add the following three fsck messages to represent the above situations:

1. "badRefContent(ERROR)": A ref has a bad content.
2. "refMissingNewline(WARN)": A valid ref does not end with newline.
3. "trailingRefContent(WARN)": A ref has trailing contents.

In order to tell whether the ref has trailing content, add a new parameter "trailing" to "parse_loose_ref_contents". Then introduce a new function "files_fsck_refs_content" to check the regular refs.

---
 Documentation/fsck-msgids.txt |  9 ++++
 fsck.h                        |  3 ++
 refs.c                        |  2 +-
 refs/files-backend.c          | 67 ++++++++++++++++++++++++++-
 refs/refs-internal.h          |  2 +-
 t/t0602-reffiles-fsck.sh      | 87 +++++++++++++++++++++++++++++++++++
 6 files changed, 166 insertions(+), 4 deletions(-)
Documentation/fsck-msgids.txt +9 -0
index 68a2801f15..1688c2f1fe 100644
@@ -19,6 +19,9 @@
 `badParentSha1`::
 	(ERROR) A commit object has a bad parent sha1.
 
+`badRefContent`::
+	(ERROR) A ref has a bad content.
+
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
 
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(WARN) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(WARN) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
 
fsck.h +3 -0
index 8894394d16..975d9b9da9 100644
@@ -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) \
@@ -73,6 +74,8 @@ enum fsck_msg_type {
 	FUNC(HAS_DOTDOT, WARN) \
 	FUNC(HAS_DOTGIT, WARN) \
 	FUNC(NULL_SHA1, WARN) \
+	FUNC(REF_MISSING_NEWLINE, WARN) \
+	FUNC(TRAILING_REF_CONTENT, WARN) \
 	FUNC(ZERO_PADDED_FILEMODE, WARN) \
 	FUNC(NUL_IN_COMMIT, WARN) \
 	FUNC(LARGE_PATHNAME, WARN) \
refs.c +1 -1
index 74de3d3009..5e74881945 100644
@@ -1758,7 +1758,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
 	}
 
 	result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
-					  oid, referent, type, failure_errno);
+					  oid, referent, type, NULL, failure_errno);
 
 done:
 	strbuf_release(&full_path);
refs/files-backend.c +65 -2

97 lines not on this page · raw message

refs/refs-internal.h +1 -1
index 2313c830d8..73b05f971b 100644
@@ -715,7 +715,7 @@ struct ref_store {
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno);
+			     const char **trailing, int *failure_errno);
 
 /*
  * Fill in the generic part of refs and add it to our collection of
t/t0602-reffiles-fsck.sh +87 -0

91 lines not on this page · raw message

Signature · 1 line
2.46.0
#10 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 1 line · shejialuo

We implicitly reply on "git-fsck(1)" to check the consistency of regular

"reply" -> "rely", I think.

Quoted 3 lines

refs. However, when parsing the regular refs for files backend, we allow the ref content to end with no newline or contain some garbages. We should warn the user about above situations.

Hmph, should we?

If the content is short (e.g., in SHA-1 repository it only has 39 hexdigit) even if that may be sufficient to uniquely name the object, we should warn about it, of course. A file that has 64-hexdigit with a terminating LF at the end may be a valid file to be in $GIT_DIR/refs/ hierarchy in a SHA-256 repository, but such a file in a SHA-1 repository should also be subject to a warning, as it could be a sign that somebody screwed up object format conversion.

But a file that has only 40-hexdigit without a terminating LF at the end? Or a file that has 40-hexdigit followed by a CRLF instead of LF? Or a file that has the identical content as a valid ref on its first line, but has extra stuff on its second and subsequent lines?

What does the name-to-object-name-mapping layer (aka "get_oid" API) do when they see such a file in the $GIT_DIR/refs/ hierarchy? If they are treated as valid ref in the "normal" code path, it needs a strong justification to tighten the rules retroactively, much stronger than "Our current code, and any of our older versions, would have written such a file as a loose ref with our code."

"What are we protecting us from with this tightening?" is the question we should be asking ourselves, when evaluating each of these new rules that fsck used not to care about.

author
#11 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 5 lines · Junio C Hamano
Quoted 1 line · shejialuo

We implicitly reply on "git-fsck(1)" to check the consistency of regular

"reply" -> "rely", I think.

I will fix in the next version.

Quoted 6 lines
Quoted 3 lines

refs. However, when parsing the regular refs for files backend, we allow the ref content to end with no newline or contain some garbages. We should warn the user about above situations.

Hmph, should we?

I am very sorry about this. Actually, I should not use "should". I don't give compelling reasons here why we need to introduce such checks. I just told the reviewer "we should warn". I will try to avoid above mistakes where I didn't give enough motivation.

Quoted 7 lines

What does the name-to-object-name-mapping layer (aka "get_oid" API) do when they see such a file in the $GIT_DIR/refs/ hierarchy? If they are treated as valid ref in the "normal" code path, it needs a strong justification to tighten the rules retroactively, much stronger than "Our current code, and any of our older versions, would have written such a file as a loose ref with our code."

Let me first talk about what will happen when we use the following command:

  $ git checkout bad-branch

I use "gdb" to find the following call sequence:

"cmd_checkout" -> "checkout_main" -> "parse_branchname_arg" -> ... -> "get_oid_basic" -> "repo_dwim_ref" -> ... -> "parse_loose_ref_contents" -> "parse_oid_hex_algop" -> "get_oid_hex_algop"

I dive into the "object-name.c::get_oid_basic" function. If we pass the actually "oid", it will call the "get_oid_hex_algop" directly. Otherwise, it will execute the following code:

  if (!len && reflog_len)
      refs_found = ...;
  else if (reflog_len)
      refs_found = ...
  else
      refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, !fatal);

if (!refs_found)

      return -1;

As we can see, when there is no corresponding refs found by calling "repo_dwim_ref" function, "get_oid_basic" function will return -1. And here we could have one important conclusion:

The "get_oid_basic" function relies on "repo_dwim_ref" function to parse the ref and get the pointee "oid". So, it uses the interfaces provided by ref backend.

Next, we look at what will "parse_loose_ref_contents" do for regular refs.

  int parse_loose_ref_contents(...)
  {
      ...
      if (parse_oid_hex_algop(buf, oid, *p, algop) ||
         (*p != '\0' && !isspace(*p))) {
            *type |= REF_ISBROKEN;
            *failure_errno = EINVAL;
            return -1;
      }
      return 0;
  }

Let's continue to see what "parse_oid_hex_algop" will do:

  int parse_oid_hex_algop(...)
  {
      int ret = get_oid_hex_algop(hex, oid, algop);
      if (!ret) {
          *end = hex + algop->hexsz;
      }
      return ret;
  }

If the result of "get_oid_hex_algop" is successful. We will set the "end" pointer here. The "get_oid_hex_algop" will eventually call the "get_hash_hex_algop" function

  static int get_hash_hex_algop(...)
  {
      int i;
      for (i = 0; i < algop->rawsz; i++) {
          int val = hex2chr(hex);
          if (val < 0)
              return -1;
          *hash+= = val;
          hex += 2;
      }
      return 0;
  }

This function will convert the hex to char by the raw size of the algorithm. And by the following code, we could conclude the following things:

1. "41053a9084501db79c72b14e8a5a0b67de3f91ae" is correct, because it will be parsed successfully by "get_hash_hex_algop" and "*p == '\0'".
2. "41053a9084501db79c72b14e8a5a0b67de3f91aef" is not correct, it will be parsed successfully by "get_hash_hex_algop" but "*p != '\0'" and "isspace(*p)" is false. So the check in "parse_loose_ref_contents" cannot be passed.
3. "1053a9084501db79c72b14e8a5a0b67de3f91a" is not correct, it cannot be parsed successfully by "get_hash_hex_algop".
4. "41053a9084501db79c72b14e8a5a0b67de3f91ae garbage" is correct, because it will be parsed successfully by "get_hash_hex_algop" and "isspace(*p)" is true.

By the above discussion, I could answer you comments one by one.

Quoted 3 lines

If the content is short (e.g., in SHA-1 repository it only has 39 hexdigit) even if that may be sufficient to uniquely name the object, we should warn about it, of course.

When the content is short, although it may be sufficient to identify the object, we should still report an error here. This is because we care about the ref. As we can see from above discussion, the "object-name.c" totally relies on the interfaces provided by the ref backend. And "get_hash_hex_algop" is unhappy about this situation. And eventually the "object-name.c::get_oid_basic" will be unhappy, return -1.

Quoted 5 lines

A file that has 64-hexdigit with a terminating LF at the end may be a valid file to be in $GIT_DIR/refs/ hierarchy in a SHA-256 repository, but such a file in a SHA-1 repository should also be subject to a warning, as it could be a sign that somebody screwed up object format conversion.

I agree with this idea. But in this implementation, we want to reuse the "parse_loose_ref_contents" to check the consistency of the regular refs. If we are in a SHA-1 repository, "parse_loose_ref_contents" will be unhappy about this. However, I don't think we need to provide user that "the content is 64-hexdigit ...". We just report "bad ref content" to the user. This will also indicate the user something is wrong, you need to check the ref database.

Quoted 4 lines

But a file that has only 40-hexdigit without a terminating LF at the end? Or a file that has 40-hexdigit followed by a CRLF instead of LF? Or a file that has the identical content as a valid ref on its first line, but has extra stuff on its second and subsequent lines?

This is the core problem why we want to introduce more strict check. Because in the current "parse_loose_ref_contents" function, as long as the next byte of the end of the hex is '\0', spaces, LF, CRLF. We could know that the content of the ref is OK.

But in my view, we should warn the user about this situation. This is because in the original code, we do not check the ref strictly for files backend. And I think at current, the normal user should not interact with the git database. If there are some garbages we found in the ref database, I guess this could be a sign for the user: "Watch out! there may be something wrong".

Quoted 3 lines

"What are we protecting us from with this tightening?" is the question we should be asking ourselves, when evaluating each of these new rules that fsck used not to care about.

That's a hard question, really. I find it hard to know what should we do? The motivation is hard to describe. But I think this reply could make thing more clear here.

Thanks,
Jialuo

#12 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

In reply to Junio C Hamano
Quoted 33 lines · Junio C Hamano
Quoted 1 line · shejialuo

We implicitly reply on "git-fsck(1)" to check the consistency of regular

"reply" -> "rely", I think.

Quoted 3 lines

refs. However, when parsing the regular refs for files backend, we allow the ref content to end with no newline or contain some garbages. We should warn the user about above situations.

Hmph, should we?

If the content is short (e.g., in SHA-1 repository it only has 39 hexdigit) even if that may be sufficient to uniquely name the object, we should warn about it, of course. A file that has 64-hexdigit with a terminating LF at the end may be a valid file to be in $GIT_DIR/refs/ hierarchy in a SHA-256 repository, but such a file in a SHA-1 repository should also be subject to a warning, as it could be a sign that somebody screwed up object format conversion.

But a file that has only 40-hexdigit without a terminating LF at the end? Or a file that has 40-hexdigit followed by a CRLF instead of LF? Or a file that has the identical content as a valid ref on its first line, but has extra stuff on its second and subsequent lines?

What does the name-to-object-name-mapping layer (aka "get_oid" API) do when they see such a file in the $GIT_DIR/refs/ hierarchy? If they are treated as valid ref in the "normal" code path, it needs a strong justification to tighten the rules retroactively, much stronger than "Our current code, and any of our older versions, would have written such a file as a loose ref with our code."

Quoted 3 lines

"What are we protecting us from with this tightening?" is the question we should be asking ourselves, when evaluating each of these new rules that fsck used not to care about.

I'd say filesystem corruption, buggy implementations and compatibility with other implementations of Git. The format for refs does not allow for any other information than either an object ID for plain refs, and the referee for symbolic refs. The fact that we do accept that is a mere implementation detail because we reuse the same function to parse refs that we also use for pseudorefs. And these _can_ have additional data.

So any reference that contains additional data is not a proper ref and thus should be warned about from my point of view. No Git tooling should write them, so if something does it's a red flag to me.

Patrick

#13 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 3 lines · Patrick Steinhardt

So any reference that contains additional data is not a proper ref and thus should be warned about from my point of view. No Git tooling should write them, so if something does it's a red flag to me.

If you find such a file in $GIT_DIR/refs/ hierarchy, because our consumer side has been looser than necessary forever, and we never have written such a file ourselves, it is a sign that a third-party tool wrote it, and that the third-party tool used our reader implementation as the specification. That is why I am hesitant to retroactively tighten the rules like this patch does.

Thanks.

#14 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 6 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

So any reference that contains additional data is not a proper ref and thus should be warned about from my point of view. No Git tooling should write them, so if something does it's a red flag to me.

Quoted 6 lines

If you find such a file in $GIT_DIR/refs/ hierarchy, because our consumer side has been looser than necessary forever, and we never have written such a file ourselves, it is a sign that a third-party tool wrote it, and that the third-party tool used our reader implementation as the specification. That is why I am hesitant to retroactively tighten the rules like this patch does.

I forgot to add my recommended course of action, without which a review is worth much less X-<.

I am OK if we tightened the rules retroactively, as long as it starts as a probing check (i.e. "info: we found an unusual thing in the wild. Please report this to us so that we can ask you for more details like how such a ref that would violate a rule that was retroactively tightened got there", not "error: malformed ref").

Thanks.

#15 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 15 lines · Junio C Hamano
Quoted 6 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

So any reference that contains additional data is not a proper ref and thus should be warned about from my point of view. No Git tooling should write them, so if something does it's a red flag to me.

Quoted 6 lines

If you find such a file in $GIT_DIR/refs/ hierarchy, because our consumer side has been looser than necessary forever, and we never have written such a file ourselves, it is a sign that a third-party tool wrote it, and that the third-party tool used our reader implementation as the specification. That is why I am hesitant to retroactively tighten the rules like this patch does.

Quoted 8 lines

I forgot to add my recommended course of action, without which a review is worth much less X-<.

I am OK if we tightened the rules retroactively, as long as it starts as a probing check (i.e. "info: we found an unusual thing in the wild. Please report this to us so that we can ask you for more details like how such a ref that would violate a rule that was retroactively tightened got there", not "error: malformed ref").

Okay, that makes sense. The fsck infrastructure does have info message types, so this should certainly be doable. I'd argue that we might want to make this an FSCK_WARN, but I'm also fine with iteratively bumping up the severity from INFO to WARN to ERROR when we don't observe any complaints about this tightening.

Patrick

author
#16 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 25 lines · Patrick Steinhardt
Quoted 15 lines · Junio C Hamano
Quoted 6 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

So any reference that contains additional data is not a proper ref and thus should be warned about from my point of view. No Git tooling should write them, so if something does it's a red flag to me.

Quoted 6 lines

If you find such a file in $GIT_DIR/refs/ hierarchy, because our consumer side has been looser than necessary forever, and we never have written such a file ourselves, it is a sign that a third-party tool wrote it, and that the third-party tool used our reader implementation as the specification. That is why I am hesitant to retroactively tighten the rules like this patch does.

Quoted 8 lines

I forgot to add my recommended course of action, without which a review is worth much less X-<.

I am OK if we tightened the rules retroactively, as long as it starts as a probing check (i.e. "info: we found an unusual thing in the wild. Please report this to us so that we can ask you for more details like how such a ref that would violate a rule that was retroactively tightened got there", not "error: malformed ref").

Quoted 6 lines

Okay, that makes sense. The fsck infrastructure does have info message types, so this should certainly be doable. I'd argue that we might want to make this an FSCK_WARN, but I'm also fine with iteratively bumping up the severity from INFO to WARN to ERROR when we don't observe any complaints about this tightening.

From the perspective of the implementation, there is no difference between the info and warn. But I have a doubt here. Do we really distinguish the info and warn in code?

Let's see the "fsck_vreport" (although this is a new function, but I never change the implementation) function:

  static int fsck_vreport(...)
  {
      enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options);
      if (msg_type == FSCK_FATAL)
          msg_type = FSCK_ERROR;
      if (msg_type == FSCK_INFO)
          msg_type = FSCK_WARN;

      ...

}

We eventually convert the "FSCK_INFO" to "FSCK_WARN". Confusing.

#17 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

In reply to shejialuo
Quoted 9 lines · shejialuo
refs/files-backend.c +9 -0
+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 fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
+	struct strbuf ref_content = STRBUF_INIT;
+	struct strbuf referent = STRBUF_INIT;
+	struct strbuf refname = STRBUF_INIT;

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3455

Quoted 10 lines
refs/files-backend.c +10 -0
+	const char *trailing = NULL;
+	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_ISREG(iter->st.st_mode)) {

We can avoid having to indent the remainder of this function if we goto cleanup here.

Patrick

author
#18 lore raw

Re: [PATCH v1 2/4] ref: add regular ref content check for files backend

Quoted 4 lines · Patrick Steinhardt

We can avoid having to indent the remainder of this function if we goto cleanup here.

Yes, actually I have thought about this way. But I don't want to use "goto". However, ident is noisy too. I will fix in the next version.

author
#19 lore raw

PATCH 3/4 ref: add symbolic ref content check for files backend

In reply to shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any non-null trailing garbage. The following are bad symbolic contents.

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will traverse the "pointee" to report the user whether this is null-garbage or no newline. And if symbolic refs contain non-null garbage, we will report "FSCK_MSG_BAD_REF_CONTENT" to the user.

Then, we will check the name of the "pointee" is correct by using "check_refname_format". And then if we can access the "pointee_path" in the file system, we should ensure that the file type is correct.

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 87 +++++++++++++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 52 +++++++++++++++++++++
 4 files changed, 143 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index 1688c2f1fe..73587661dc 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefPointee`::
+	(ERROR) The pointee of a symref is bad.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index 975d9b9da9..985b674dd9 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_POINTEE, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +87 -0

111 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +52 -0

56 lines not on this page · raw message

Signature · 1 line
2.46.0
#20 lore raw

Re: [PATCH v1 3/4] ref: add symbolic ref content check for files backend

Quoted 93 lines · shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any non-null trailing garbage. The following are bad symbolic contents.

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will traverse the "pointee" to report the user whether this is null-garbage or no newline. And if symbolic refs contain non-null garbage, we will report "FSCK_MSG_BAD_REF_CONTENT" to the user.

Then, we will check the name of the "pointee" is correct by using "check_refname_format". And then if we can access the "pointee_path" in the file system, we should ensure that the file type is correct.

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 87 +++++++++++++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 52 +++++++++++++++++++++
 4 files changed, 143 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index 1688c2f1fe..73587661dc 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefPointee`::
+	(ERROR) The pointee of a symref is bad.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index 975d9b9da9..985b674dd9 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_POINTEE, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +16 -0
index ae71692f36..bfb8d338d2 100644
@@ -3434,12 +3434,92 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  const char *refs_check_dir,
 				  struct dir_iterator *iter);
 
+/*
+ * Check the symref "pointee_name" and "pointee_path". The caller should
+ * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
+ * would be the content after "refs:".
+ */
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    const char *refname,
+				    struct strbuf *pointee_name,
+				    struct strbuf *pointee_path)
+{
+	unsigned int newline_num = 0;
+	unsigned int space_num = 0;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3462

Quoted 10 lines
refs/files-backend.c +10 -0
+
+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	while (*p != '\0') {

We typically write this while (*p).

Quoted 4 lines
refs/files-backend.c +4 -0
+		if ((space_num || newline_num) && !isspace(*p)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_BAD_REF_CONTENT,
+					      "contains non-null garbage");

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3476

Quoted 10 lines
refs/files-backend.c +10 -0
+			goto out;
+		}
+
+		if (*p == '\n') {
+			newline_num++;
+		} else if (*p == ' ') {
+			space_num++;
+		}
+		p++;
+	}

Can't we replace this with a single strchr('\n') call to check for the newline and then verify that the next character is a \0? The check for spaces would then be handled by check_refname_format().

Quoted 5 lines
refs/files-backend.c +5 -0
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3510

Quoted 10 lines
refs/files-backend.c +10 -0
+	 */
+	if (lstat(pointee_path->buf, &st) < 0)
+		goto out;
+
+	if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to an invalid file type");
+		goto out;
+	}

What exactly are we guarding against here? Don't we already verify that files in refs/ have the correct type? Or are we checking that it does not point to a directory?

Patrick

author
#21 lore raw

Re: [PATCH v1 3/4] ref: add symbolic ref content check for files backend

Quoted 10 lines · Patrick Steinhardt
Quoted 10 lines
refs/files-backend.c +10 -0
+		if ((space_num || newline_num) && !isspace(*p)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_BAD_REF_CONTENT,
+					      "contains non-null garbage");
+			goto out;
+		}
+
+		if (*p == '\n') {
+			newline_num++;
+		} else if (*p == ' ') {
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3476

Quoted 4 lines
refs/files-backend.c +4 -0
+			space_num++;
+		}
+		p++;
+	}

Can't we replace this with a single strchr('\n') call to check for the newline and then verify that the next character is a \0? The check for spaces would then be handled by check_refname_format().

We cannot. Think about this situation.

"ref: refs/heads/master \n "

We find that the next character of '\n' is not '\0'. Then we leave it to "check_refname_format". But "check_refname_format" will report an error here, but this is an allowed symref.

But I think using strchr is a nice way. I will try to find an elegant way here to handle this logic here.

Quoted 11 lines
Quoted 11 lines
refs/files-backend.c +11 -0
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */
+	if (lstat(pointee_path->buf, &st) < 0)
+		goto out;
+
+	if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3510

Quoted 4 lines
refs/files-backend.c +4 -0
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to an invalid file type");
+		goto out;
+	}

What exactly are we guarding against here? Don't we already verify that files in refs/ have the correct type? Or are we checking that it does not point to a directory?

When scanning the "refs" directory, we will check the file in the ref database, but we ignore the directory. So we are checking to know whether it does not point to a directory. If the ref points to a bad file type for example "ref/heads/bad-file"

If it is a block type file. We will first report that "refs/heads/bad-file" is a bad file and then report ref points to bad file "refs/heads/bad-file".

Actually, I think this is a little redundant here, but we can be tolerant about this because we need to guard against directory. We need to consider this situation.

So we could let this be.

Quoted 1 line

Patrick

#22 lore raw

Re: [PATCH v1 3/4] ref: add symbolic ref content check for files backend

Quoted 23 lines · shejialuo
Quoted 10 lines · Patrick Steinhardt
Quoted 10 lines
refs/files-backend.c +10 -0
+		if ((space_num || newline_num) && !isspace(*p)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_BAD_REF_CONTENT,
+					      "contains non-null garbage");
+			goto out;
+		}
+
+		if (*p == '\n') {
+			newline_num++;
+		} else if (*p == ' ') {
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3476

Quoted 4 lines
refs/files-backend.c +4 -0
+			space_num++;
+		}
+		p++;
+	}

Can't we replace this with a single strchr('\n') call to check for the newline and then verify that the next character is a \0? The check for spaces would then be handled by check_refname_format().

Quoted 7 lines

We cannot. Think about this situation.

"ref: refs/heads/master \n "

We find that the next character of '\n' is not '\0'. Then we leave it to "check_refname_format". But "check_refname_format" will report an error here, but this is an allowed symref.

Wouldn't it be correct to warn about this? To me the above very much looks like garbage after the refname, same like we'd also warn about such garbage for direct refs.

Patrick

author
#23 lore raw

Re: [PATCH v1 3/4] ref: add symbolic ref content check for files backend

Quoted 28 lines · Patrick Steinhardt
Quoted 27 lines · shejialuo
Quoted 10 lines · Patrick Steinhardt
Quoted 10 lines
refs/files-backend.c +10 -0
+		if ((space_num || newline_num) && !isspace(*p)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_BAD_REF_CONTENT,
+					      "contains non-null garbage");
+			goto out;
+		}
+
+		if (*p == '\n') {
+			newline_num++;
+		} else if (*p == ' ') {
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3476

Quoted 4 lines
refs/files-backend.c +4 -0
+			space_num++;
+		}
+		p++;
+	}

Can't we replace this with a single strchr('\n') call to check for the newline and then verify that the next character is a \0? The check for spaces would then be handled by check_refname_format().

We cannot. Think about this situation.

"ref: refs/heads/master \n "

Quoted 8 lines
Quoted 3 lines

We find that the next character of '\n' is not '\0'. Then we leave it to "check_refname_format". But "check_refname_format" will report an error here, but this is an allowed symref.

Wouldn't it be correct to warn about this? To me the above very much looks like garbage after the refname, same like we'd also warn about such garbage for direct refs.

Yes, we should warn about this. But only null-garbage is allowed for symref. The following situation is bad:

"ref: refs/heads/master \n garbage\n"

We should report error here, from my perspective, it's a FATAL ERROR. However, let's decide how to do this when we know what fsck error level we should set.

Quoted 1 line

Patrick

author
#24 lore raw

PATCH 4/4 ref: add symlink ref consistency check for files backend

In reply to shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which are legacy symbolic links. We should not check the trailing garbage for symbolic links. Add a new parameter "symbolic_link" to disable some checks which should only be used for symbolic ref.

We firstly use the "strbuf_add_real_path" to resolve the symlinks and get the absolute path "pointee_path" which the symlink ref points to. Then we can get the absolute path "abs_gitdir" of the "gitdir". By combining "pointee_path" and "abs_gitdir", we can extract the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

---
 refs/files-backend.c     | 82 ++++++++++++++++++++++++++++------------
 t/t0602-reffiles-fsck.sh | 44 +++++++++++++++++++++
 2 files changed, 101 insertions(+), 25 deletions(-)
refs/files-backend.c +57 -25
index bfb8d338d2..398afedaf0 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../abspath.h"
 #include "../copy.h"
 #include "../environment.h"
 #include "../gettext.h"
@@ -3437,13 +3438,15 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 /*
  * Check the symref "pointee_name" and "pointee_path". The caller should
  * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
- * would be the content after "refs:".
+ * would be the content after "refs:". For symblic link, "pointee_name" would
+ * be the relative path agaignst "gitdir".
  */
 static int files_fsck_symref_target(struct fsck_options *o,
 				    struct fsck_ref_report *report,
 				    const char *refname,
 				    struct strbuf *pointee_name,
-				    struct strbuf *pointee_path)
+				    struct strbuf *pointee_path,
+				    unsigned int symbolic_link)
 {
 	unsigned int newline_num = 0;
 	unsigned int space_num = 0;
@@ -3459,34 +3462,36 @@ static int files_fsck_symref_target(struct fsck_options *o,
 		goto out;
 	}
 
-	while (*p != '\0') {
-		if ((space_num || newline_num) && !isspace(*p)) {
-			ret = fsck_report_ref(o, report,
-					      FSCK_MSG_BAD_REF_CONTENT,
-					      "contains non-null garbage");
-			goto out;
+	if (!symbolic_link) {
+		while (*p != '\0') {
+			if ((space_num || newline_num) && !isspace(*p)) {
+				ret = fsck_report_ref(o, report,
+						      FSCK_MSG_BAD_REF_CONTENT,
+						      "contains non-null garbage");
+				goto out;
+			}
+
+			if (*p == '\n') {
+				newline_num++;
+			} else if (*p == ' ') {
+				space_num++;
+			}
+			p++;
 		}
 
-		if (*p == '\n') {
-			newline_num++;
-		} else if (*p == ' ') {
-			space_num++;
+		if (space_num || newline_num > 1) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing null-garbage");
+		} else if (!newline_num) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_REF_MISSING_NEWLINE,
+					      "missing newline");
 		}
-		p++;
-	}
 
-	if (space_num || newline_num > 1) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_TRAILING_REF_CONTENT,
-				      "trailing null-garbage");
-	} else if (!newline_num) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_REF_MISSING_NEWLINE,
-				      "missing newline");
+		strbuf_rtrim(pointee_name);
 	}
 
-	strbuf_rtrim(pointee_name);
-
 	if (check_refname_format(pointee_name->buf, 0)) {
 		ret = fsck_report_ref(o, report,
 				      FSCK_MSG_BAD_SYMREF_POINTEE,
@@ -3521,8 +3526,10 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
 	struct strbuf pointee_path = STRBUF_INIT;
 	struct strbuf ref_content = STRBUF_INIT;
+	struct strbuf abs_gitdir = STRBUF_INIT;
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
+	unsigned int symbolic_link = 0;
 	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
@@ -3567,8 +3574,32 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 				    ref_store->gitdir, referent.buf);
 			ret = files_fsck_symref_target(o, &report, refname.buf,
 						       &referent,
-						       &pointee_path);
+						       &pointee_path,
+						       symbolic_link);
+		}
+	} else if (S_ISLNK(iter->st.st_mode)) {
+		const char *pointee_name = NULL;
+
+		symbolic_link = 1;
+
+		strbuf_add_real_path(&pointee_path, iter->path.buf);
+		strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
+		strbuf_normalize_path(&abs_gitdir);
+		if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
+			strbuf_addch(&abs_gitdir, '/');
+
+		if (!skip_prefix(pointee_path.buf,
+				 abs_gitdir.buf, &pointee_name)) {
+			ret = fsck_report_ref(o, &report,
+					       FSCK_MSG_BAD_SYMREF_POINTEE,
+					       "point to target outside gitdir");
+			goto cleanup;
 		}
+
+		strbuf_addstr(&referent, pointee_name);
+		ret = files_fsck_symref_target(o, &report, refname.buf,
+					       &referent, &pointee_path,
+					       symbolic_link);
 	}
 
 cleanup:
@@ -3576,6 +3607,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	strbuf_release(&ref_content);
 	strbuf_release(&referent);
 	strbuf_release(&pointee_path);
+	strbuf_release(&abs_gitdir);
 	return ret;
 }
 
t/t0602-reffiles-fsck.sh +44 -0

48 lines not on this page · raw message

Signature · 1 line
2.46.0
author
#25 lore raw

v2 cover add ref content check for files backend

In reply to shejialuo

Hi All:

This new version handles the following reviews:

1. According to the advice from the Junio, we should just use "{0}" to initialize the zero structure "fsck_ref_report". This version handles this in [PATCH v2 1/4].
2. According to the advice from the Patrick, use "strrchr" instead of looping to make the code more clean in [PATCH v2 3/4].
3. Use "goto" to remove ident.

However, the most important thing for this patch is which fsck message type I choose. I have recorded the reason in the commit message. But I wanna explain the motivation in cover letter for making the reviewers easy to understand.

Actually, in the review process of the first version. Junio thought we should use "FSCK_INFO" and Patrick thought we should use "FSCK_WARN". And I raised a question here, what is the difference between the "FSCK_INFO" and "FSCK_WARN" because in "fsck.c::fsck_vreport" function, we will convert "FSCK_INFO" to "FSCK_WARN" like the following:

    static int fsck_vreport(...)
    {
        enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options);

        if (msg_type == FSCK_FATAL)
            msg_type = FSCK_ERROR;
        if (msg_type == FSCK_INFO)
             msg_type = FSCK_WARN;
        ...
    }

And I have gone back to the history. Actually the first time the fsck message type was set up at f27d05b170 (fsck: allow upgrading fsck warnings to errors, 2015-06-22):

https://lore.kernel.org/git/[email protected]/

And I have understood why we need "FSCK_INFO". This is because when setting the "strict" filed in "fsck_options", all the fsck warns will become fsck errors. For example, this change verifies my thinking:
4dd3b045f5 (fsck: downgrade tree badFilemode to "info", 2022-08-10).

As you can see, this restriction makes the code safer. So, I agree with Junio, at now, we should use "FSCK_INFO" for trailing garbage and ref content ends without newline.

But we should report fsck errors for the following two situations for "git-fsck(1)" will report fsck errors by implicitly checking the ref database consistency.

1. "parse_loose_ref_contents" fail.
2. symref content is bad (cannot parse).

Thanks,
Jialuo

shejialuo (4):
ref: initialize "fsck_ref_report" with zero
ref: add regular ref content check for files backend
ref: add symbolic ref content check for files backend
ref: add symlink ref check for files backend

 Documentation/fsck-msgids.txt |  12 +++
 fsck.h                        |   4 +
 refs.c                        |   2 +-
 refs/files-backend.c          | 179 +++++++++++++++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 185 ++++++++++++++++++++++++++++++++++
 6 files changed, 379 insertions(+), 5 deletions(-)

Range-diff against v1:
1: 9ed3026ac5 ! 1: 0367904c81 fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro
@@ Metadata
Author: shejialuo <[email protected]>

## Commit message ##
- fsck: introduce "FSCK_REF_REPORT_DEFAULT" macro
+ ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

- In order to conveniently create a new "fsck_ref_report", add a new macro
- "FSCK_REF_REPORT_DEFAULT".
+ The original code explicitly specifies the ".path" field to initialize
+ the "fsck_ref_report" structure. However, it introduces confusion how we
+ initialize the other fields. In order to avoid this, initialize the
+ "fsck_ref_report" with zero to make clear that everything in
+ "fsck_ref_report" is zero initialized.

- ## fsck.h ##
-@@ fsck.h: struct fsck_ref_report {
- const char *referent;
- };
-
-+#define FSCK_REF_REPORT_DEFAULT { \
-+ .path = NULL, \
-+ .oid = NULL, \
-+ .referent = NULL, \
-+}
-+
- struct fsck_options {
- fsck_walk_func walk;
- fsck_error error_func;
-
## refs/files-backend.c ##
@@ refs/files-backend.c: static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
goto cleanup;

if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
- struct fsck_ref_report report = { .path = NULL };
-+ struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
++ struct fsck_ref_report report = {0};

strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path); report.path = sb.buf;
2: 714284cf2b ! 2: 7b6f4145cd ref: add regular ref content check for files backend
@@ Metadata
## Commit message ##
ref: add regular ref content check for files backend

- We implicitly reply on "git-fsck(1)" to check the consistency of regular
- refs. However, when parsing the regular refs for files backend, we allow
- the ref content to end with no newline or contain some garbages. We
- should warn the user about above situations.
+ We implicitly rely on "git-fsck(1)" to check the consistency of regular
+ refs. However, when parsing the regular refs for files backend by using
+ "files-backend.c::parse_loose_ref_contents", we allow the ref content to
+ be end with no newline or contain some garbages.

- In order to provide above functionality, enhance the "git-refs verify"
- command by adding consistency check for regular refs for files backend.
+ It may seem that we should report an error or warn fsck message to the
+ user about above situations. However, there may be some third-party
+ tools customizing the content of refs. We should not report an error
+ fsck message.

- Add the following three fsck messages to represent the above situations:
+ And we cannot either report a warn fsck message to the user. This is
+ because for "git-receive-pack(1)" and "git-fetch-pack(1)", they will
+ parse the fsck message type and check the message type by
+ "fsck.c::is_valid_msg_type". Only the fsck infos are not valid. If we
+ make the fsck message type to be warn, the user could upgrade the fsck
+ warnings to errors. And the user can also set the "strict" field in
+ "fsck_options" to upgrade the fsck warnings to errors.

- 1. "badRefContent(ERROR)": A ref has a bad content.
- 2. "refMissingNewline(WARN)": A valid ref does not end with newline.
- 3. "trailingRefContent(WARN)": A ref has trailing contents.
+ We should not allow the user to upgrade the fsck warnings to errors. It
+ might cause compatibility issue which will break the legacy repository.
+ So we add the following two fsck infos to represent the situation where
+ the ref content ends without newline or has garbages:
+
+ 1. "refMissingNewline(INFO)": A valid ref does not end with newline.
+ 2. "trailingRefContent(INFO)": A ref has trailing contents.
+
+ In "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN",
+ and we can still warn the user about these situations when using
+ "git-refs verify" without introducing compatibility issue.
+
+ In current "git-fsck(1)", it will report an error when the ref content
+ is bad, so we should following this to report an error to the user when
+ "parse_loose_ref_contents" fails. And we add a new fsck error message
+ called "badRefContent(ERROR)" to represent that a ref has a bad content.

In order to tell whether the ref has trailing content, add a new parameter "trailing" to "parse_loose_ref_contents". Then introduce a new
- function "files_fsck_refs_content" to check the regular refs.
+ function "files_fsck_refs_content" to check the regular refs to enhance
+ the "git-refs verify".

@@ Documentation/fsck-msgids.txt
(WARN) Tree contains entries pointing to a null sha1.

+refMissingNewline::
-+ (WARN) A valid ref does not end with newline.
++ (INFO) A valid ref does not end with newline.
+
+trailingRefContent::
-+ (WARN) A ref has trailing contents.
++ (INFO) A ref has trailing contents.
+
treeNotSorted::
(ERROR) A tree is not properly sorted.
@@ fsck.h: enum fsck_msg_type {
FUNC(BAD_REF_NAME, ERROR) \
FUNC(BAD_TIMEZONE, ERROR) \
@@ fsck.h: enum fsck_msg_type {
- FUNC(HAS_DOTDOT, WARN) \
- FUNC(HAS_DOTGIT, WARN) \
- FUNC(NULL_SHA1, WARN) \
-+ FUNC(REF_MISSING_NEWLINE, WARN) \
-+ FUNC(TRAILING_REF_CONTENT, WARN) \
- FUNC(ZERO_PADDED_FILEMODE, WARN) \
- FUNC(NUL_IN_COMMIT, WARN) \
- FUNC(LARGE_PATHNAME, WARN) \
+ FUNC(MAILMAP_SYMLINK, INFO) \
+ FUNC(BAD_TAG_NAME, INFO) \
+ FUNC(MISSING_TAGGER_ENTRY, INFO) \
++ FUNC(REF_MISSING_NEWLINE, INFO) \
++ FUNC(TRAILING_REF_CONTENT, INFO) \
+ /* ignored (elevated when requested) */ \
+ FUNC(EXTRA_HEADER_ENTRY, IGNORE)
+

## refs.c ##
@@ refs.c: static int refs_read_special_head(struct ref_store *ref_store,
@@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
+ const char *refs_check_dir,
+ struct dir_iterator *iter)
+{
-+ struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
+ struct strbuf ref_content = STRBUF_INIT;
+ struct strbuf referent = STRBUF_INIT;
+ struct strbuf refname = STRBUF_INIT;
++ struct fsck_ref_report report = {0};
+ const char *trailing = NULL;
+ unsigned int type = 0;
+ int failure_errno = 0;
@@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
+ }
+
+ if (parse_loose_ref_contents(ref_store->repo->hash_algo,
-+ ref_content.buf, &oid, &referent,
-+ &type, &trailing, &failure_errno)) {
++ ref_content.buf, &oid, &referent,
++ &type, &trailing, &failure_errno)) {
+ ret = fsck_report_ref(o, &report,
+ FSCK_MSG_BAD_REF_CONTENT,
+ "invalid ref content");
@@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
+ goto cleanup;
+ }
+ }
++ goto cleanup;
+ }
+
+cleanup:
3: 032b0d6a64 ! 3: 20d8556902 ref: add symbolic ref content check for files backend

    @@ Commit message
         3. "ref: refs/heads/master\n\n"
     
         But we do not allow any non-null trailing garbage. The following are bad
    -    symbolic contents.
    +    symbolic contents which will be reported as fsck error by "git-fsck(1)".
     
         1. "ref: refs/heads/master garbage\n"
         2. "ref: refs/heads/master \n\n\n garbage  "
     
    -    In order to provide above checks, we will traverse the "pointee" to
    -    report the user whether this is null-garbage or no newline. And if
    -    symbolic refs contain non-null garbage, we will report
    -    "FSCK_MSG_BAD_REF_CONTENT" to the user.
    -
    -    Then, we will check the name of the "pointee" is correct by using
    -    "check_refname_format". And then if we can access the "pointee_path" in
    -    the file system, we should ensure that the file type is correct.
    +    In order to provide above checks, we will use "strrchr" to check whether
    +    we have newline in the ref content. Then we will check the name of the
    +    "pointee" is correct by using "check_refname_format". If the function
    +    fails, we need to trim the "pointee" to see whether the null-garbage
    +    causes the function fails. If so, we need to report that there is
    +    null-garbage in the symref content. Otherwise, we should report the user
    +    the "pointee" is bad.
     
         Mentored-by: Patrick Steinhardt <[email protected]>
         Mentored-by: Karthik Nayak <[email protected]>
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
     +				    struct strbuf *pointee_name,
     +				    struct strbuf *pointee_path)
     +{
    -+	unsigned int newline_num = 0;
    -+	unsigned int space_num = 0;
    ++	const char *newline_pos = NULL;
     +	const char *p = NULL;
     +	struct stat st;
     +	int ret = 0;
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
     +		goto out;
     +	}
     +
    -+	while (*p != '\0') {
    -+		if ((space_num || newline_num) && !isspace(*p)) {
    -+			ret = fsck_report_ref(o, report,
    -+					      FSCK_MSG_BAD_REF_CONTENT,
    -+					      "contains non-null garbage");
    -+			goto out;
    -+		}
    -+
    -+		if (*p == '\n') {
    -+			newline_num++;
    -+		} else if (*p == ' ') {
    -+			space_num++;
    -+		}
    -+		p++;
    -+	}
    -+
    -+	if (space_num || newline_num > 1) {
    -+		ret = fsck_report_ref(o, report,
    -+				      FSCK_MSG_TRAILING_REF_CONTENT,
    -+				      "trailing null-garbage");
    -+	} else if (!newline_num) {
    ++	newline_pos = strrchr(p, '\n');
    ++	if (!newline_pos || *(newline_pos + 1)) {
     +		ret = fsck_report_ref(o, report,
     +				      FSCK_MSG_REF_MISSING_NEWLINE,
     +				      "missing newline");
     +	}
     +
    -+	strbuf_rtrim(pointee_name);
    -+
     +	if (check_refname_format(pointee_name->buf, 0)) {
    ++		/*
    ++		 * When containing null-garbage, "check_refname_format" will
    ++		 * fail, we should trim the "pointee" to check again.
    ++		 */
    ++		strbuf_rtrim(pointee_name);
    ++		if (!check_refname_format(pointee_name->buf, 0)) {
    ++			ret = fsck_report_ref(o, report,
    ++					      FSCK_MSG_TRAILING_REF_CONTENT,
    ++					      "trailing null-garbage");
    ++			goto out;
    ++		}
    ++
     +		ret = fsck_report_ref(o, report,
     +				      FSCK_MSG_BAD_SYMREF_POINTEE,
     +				      "points to refname with invalid format");
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
      				   const char *refs_check_dir,
      				   struct dir_iterator *iter)
      {
    - 	struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
     +	struct strbuf pointee_path = STRBUF_INIT;
      	struct strbuf ref_content = STRBUF_INIT;
      	struct strbuf referent = STRBUF_INIT;
    @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_s
     +						       &referent,
     +						       &pointee_path);
      		}
    + 		goto cleanup;
      	}
    - 
     @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
      	strbuf_release(&refname);
      	strbuf_release(&ref_content);
    @@ t/t0602-reffiles-fsck.sh: test_expect_success 'regular ref content should be che
     +	printf "ref: refs/heads/branch     " > $branch_dir_prefix/a/b/branch-trailing &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    ++	warning: refs/heads/a/b/branch-trailing: refMissingNewline: missing newline
     +	warning: refs/heads/a/b/branch-trailing: trailingRefContent: trailing null-garbage
     +	EOF
     +	rm $branch_dir_prefix/a/b/branch-trailing &&
    @@ t/t0602-reffiles-fsck.sh: test_expect_success 'regular ref content should be che
     +	printf "ref: refs/heads/branch \n\n " > $branch_dir_prefix/a/b/branch-trailing &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    ++	warning: refs/heads/a/b/branch-trailing: refMissingNewline: missing newline
     +	warning: refs/heads/a/b/branch-trailing: trailingRefContent: trailing null-garbage
     +	EOF
     +	rm $branch_dir_prefix/a/b/branch-trailing &&
4:  147a873958 ! 4:  d9867c5f87 ref: add symlink ref consistency check for files backend
    @@ Metadata
     Author: shejialuo <[email protected]>
     
      ## Commit message ##
    -    ref: add symlink ref consistency check for files backend
    +    ref: add symlink ref check for files backend
     
         We have already introduced "files_fsck_symref_target". We should reuse
         this function to handle the symrefs which are legacy symbolic links. We
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
     +				    struct strbuf *pointee_path,
     +				    unsigned int symbolic_link)
      {
    - 	unsigned int newline_num = 0;
    - 	unsigned int space_num = 0;
    + 	const char *newline_pos = NULL;
    + 	const char *p = NULL;
     @@ refs/files-backend.c: static int files_fsck_symref_target(struct fsck_options *o,
      		goto out;
      	}
      
    --	while (*p != '\0') {
    --		if ((space_num || newline_num) && !isspace(*p)) {
    --			ret = fsck_report_ref(o, report,
    --					      FSCK_MSG_BAD_REF_CONTENT,
    --					      "contains non-null garbage");
    --			goto out;
    +-	newline_pos = strrchr(p, '\n');
    +-	if (!newline_pos || *(newline_pos + 1)) {
    +-		ret = fsck_report_ref(o, report,
    +-				      FSCK_MSG_REF_MISSING_NEWLINE,
    +-				      "missing newline");
     +	if (!symbolic_link) {
    -+		while (*p != '\0') {
    -+			if ((space_num || newline_num) && !isspace(*p)) {
    -+				ret = fsck_report_ref(o, report,
    -+						      FSCK_MSG_BAD_REF_CONTENT,
    -+						      "contains non-null garbage");
    -+				goto out;
    -+			}
    -+
    -+			if (*p == '\n') {
    -+				newline_num++;
    -+			} else if (*p == ' ') {
    -+				space_num++;
    -+			}
    -+			p++;
    - 		}
    - 
    --		if (*p == '\n') {
    --			newline_num++;
    --		} else if (*p == ' ') {
    --			space_num++;
    -+		if (space_num || newline_num > 1) {
    -+			ret = fsck_report_ref(o, report,
    -+					      FSCK_MSG_TRAILING_REF_CONTENT,
    -+					      "trailing null-garbage");
    -+		} else if (!newline_num) {
    ++		newline_pos = strrchr(p, '\n');
    ++		if (!newline_pos || *(newline_pos + 1)) {
     +			ret = fsck_report_ref(o, report,
     +					      FSCK_MSG_REF_MISSING_NEWLINE,
     +					      "missing newline");
    - 		}
    --		p++;
    --	}
    - 
    --	if (space_num || newline_num > 1) {
    --		ret = fsck_report_ref(o, report,
    --				      FSCK_MSG_TRAILING_REF_CONTENT,
    --				      "trailing null-garbage");
    --	} else if (!newline_num) {
    --		ret = fsck_report_ref(o, report,
    --				      FSCK_MSG_REF_MISSING_NEWLINE,
    --				      "missing newline");
    -+		strbuf_rtrim(pointee_name);
    ++		}
      	}
      
    --	strbuf_rtrim(pointee_name);
    --
      	if (check_refname_format(pointee_name->buf, 0)) {
    +-		/*
    +-		 * When containing null-garbage, "check_refname_format" will
    +-		 * fail, we should trim the "pointee" to check again.
    +-		 */
    +-		strbuf_rtrim(pointee_name);
    +-		if (!check_refname_format(pointee_name->buf, 0)) {
    +-			ret = fsck_report_ref(o, report,
    +-					      FSCK_MSG_TRAILING_REF_CONTENT,
    +-					      "trailing null-garbage");
    +-			goto out;
    ++		if (!symbolic_link) {
    ++			/*
    ++			* When containing null-garbage, "check_refname_format" will
    ++			* fail, we should trim the "pointee" to check again.
    ++			*/
    ++			strbuf_rtrim(pointee_name);
    ++			if (!check_refname_format(pointee_name->buf, 0)) {
    ++				ret = fsck_report_ref(o, report,
    ++						      FSCK_MSG_TRAILING_REF_CONTENT,
    ++						      "trailing null-garbage");
    ++				goto out;
    ++			}
    + 		}
    + 
      		ret = fsck_report_ref(o, report,
    - 				      FSCK_MSG_BAD_SYMREF_POINTEE,
     @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    - 	struct fsck_ref_report report = FSCK_REF_REPORT_DEFAULT;
    + {
      	struct strbuf pointee_path = STRBUF_INIT;
      	struct strbuf ref_content = STRBUF_INIT;
     +	struct strbuf abs_gitdir = STRBUF_INIT;
      	struct strbuf referent = STRBUF_INIT;
      	struct strbuf refname = STRBUF_INIT;
    + 	struct fsck_ref_report report = {0};
    ++	const char *pointee_name = NULL;
     +	unsigned int symbolic_link = 0;
      	const char *trailing = NULL;
      	unsigned int type = 0;
    @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_s
     -						       &pointee_path);
     +						       &pointee_path,
     +						       symbolic_link);
    -+		}
    -+	} else if (S_ISLNK(iter->st.st_mode)) {
    -+		const char *pointee_name = NULL;
    + 		}
    + 		goto cleanup;
    + 	}
    + 
    ++	symbolic_link = 1;
     +
    -+		symbolic_link = 1;
    ++	strbuf_add_real_path(&pointee_path, iter->path.buf);
    ++	strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
    ++	strbuf_normalize_path(&abs_gitdir);
    ++	if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
    ++		strbuf_addch(&abs_gitdir, '/');
     +
    -+		strbuf_add_real_path(&pointee_path, iter->path.buf);
    -+		strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
    -+		strbuf_normalize_path(&abs_gitdir);
    -+		if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
    -+			strbuf_addch(&abs_gitdir, '/');
    ++	if (!skip_prefix(pointee_path.buf, abs_gitdir.buf, &pointee_name)) {
    ++		ret = fsck_report_ref(o, &report,
    ++				      FSCK_MSG_BAD_SYMREF_POINTEE,
    ++				      "point to target outside gitdir");
    ++		goto cleanup;
    ++	}
     +
    -+		if (!skip_prefix(pointee_path.buf,
    -+				 abs_gitdir.buf, &pointee_name)) {
    -+			ret = fsck_report_ref(o, &report,
    -+					       FSCK_MSG_BAD_SYMREF_POINTEE,
    -+					       "point to target outside gitdir");
    -+			goto cleanup;
    - 		}
    ++	strbuf_addstr(&referent, pointee_name);
    ++	ret = files_fsck_symref_target(o, &report, refname.buf,
    ++				       &referent, &pointee_path,
    ++				       symbolic_link);
     +
    -+		strbuf_addstr(&referent, pointee_name);
    -+		ret = files_fsck_symref_target(o, &report, refname.buf,
    -+					       &referent, &pointee_path,
    -+					       symbolic_link);
    - 	}
    - 
      cleanup:
    -@@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    + 	strbuf_release(&refname);
      	strbuf_release(&ref_content);
      	strbuf_release(&referent);
      	strbuf_release(&pointee_path);
Signature · 1 line
2.46.0
author
#26 lore raw

PATCH v2 1/4 ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly specifies the ".path" field to initialize the "fsck_ref_report" structure. However, it introduces confusion how we initialize the other fields. In order to avoid this, initialize the "fsck_ref_report" with zero to make clear that everything in "fsck_ref_report" is zero initialized.

---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 8d6ec9458d..d6fc3bd67c 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = {0};
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.46.0
#27 lore raw

Re: [PATCH v2 1/4] ref: initialize "fsck_ref_report" with zero

Quoted 3 lines · shejialuo

The original code explicitly specifies the ".path" field to initialize the "fsck_ref_report" structure. However, it introduces confusion how we initialize the other fields.

The above description is a bit too strong than what this patch is actually fixing. If you explicitly initialize any member of an aggregate type, other members not mentioned will be implicitly 0-initialized, so the original does not give any confusion to readers who know what they are reading.

What the patch improves is that the common idiom used in this code base (and possibly elsewhere) is to use "{ 0 }", instead of explicitly saying "this particular member is 0-initialized".

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use "{ 0 }" to express that we are 0-initializing everything.

The patch is correct, but spelling it like "{ 0 }" with a space on both sides is more common [*], and because this patch is all about making it more idiomatic, let's write it that way.

Thanks.

[Footnote]

* "git grep -e '{0};' -e '{ 0 };' '*.[ch]'" tells us so.

Quoted 20 lines
---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 8d6ec9458d..d6fc3bd67c 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = {0};
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
author
#28 lore raw

PATCH v2 2/4 ref: add regular ref content check for files backend

In reply to shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using "files-backend.c::parse_loose_ref_contents", we allow the ref content to be end with no newline or contain some garbages.

It may seem that we should report an error or warn fsck message to the user about above situations. However, there may be some third-party tools customizing the content of refs. We should not report an error fsck message.

And we cannot either report a warn fsck message to the user. This is because if the caller set the "strict" field in "fsck_options" to to upgrade the fsck warnings to errors.

We should not allow the user to upgrade the fsck warnings to errors. It might cause compatibility issue which will break the legacy repository. So we add the following two fsck infos to represent the situation where the ref content ends without newline or has garbages:

1. "refMissingNewline(INFO)": A valid ref does not end with newline.
2. "trailingRefContent(INFO)": A ref has trailing contents.

In "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN", and we can still warn the user about these situations when using "git-refs verify" without introducing compatibility issue.

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

In order to tell whether the ref has trailing content, add a new parameter "trailing" to "parse_loose_ref_contents". Then introduce a new function "files_fsck_refs_content" to check the regular refs to enhance the "git-refs verify".

---
 Documentation/fsck-msgids.txt |  9 ++++
 fsck.h                        |  3 ++
 refs.c                        |  2 +-
 refs/files-backend.c          | 68 ++++++++++++++++++++++++++-
 refs/refs-internal.h          |  2 +-
 t/t0602-reffiles-fsck.sh      | 87 +++++++++++++++++++++++++++++++++++
 6 files changed, 167 insertions(+), 4 deletions(-)
Documentation/fsck-msgids.txt +9 -0
index 68a2801f15..fc074fc571 100644
@@ -19,6 +19,9 @@
 `badParentSha1`::
 	(ERROR) A commit object has a bad parent sha1.
 
+`badRefContent`::
+	(ERROR) A ref has a bad content.
+
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
 
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
 
fsck.h +3 -0
index 500b4c04d2..b85072df57 100644
@@ -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) \
@@ -84,6 +85,8 @@ enum fsck_msg_type {
 	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
+	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
 
refs.c +1 -1
index 74de3d3009..5e74881945 100644
@@ -1758,7 +1758,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
 	}
 
 	result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
-					  oid, referent, type, failure_errno);
+					  oid, referent, type, NULL, failure_errno);
 
 done:
 	strbuf_release(&full_path);
refs/files-backend.c +66 -2

98 lines not on this page · raw message

refs/refs-internal.h +1 -1
index 2313c830d8..73b05f971b 100644
@@ -715,7 +715,7 @@ struct ref_store {
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno);
+			     const char **trailing, int *failure_errno);
 
 /*
  * Fill in the generic part of refs and add it to our collection of
t/t0602-reffiles-fsck.sh +87 -0

91 lines not on this page · raw message

Signature · 1 line
2.46.0
author
#29 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 4 lines · shejialuo

And we cannot either report a warn fsck message to the user. This is because if the caller set the "strict" field in "fsck_options" to to upgrade the fsck warnings to errors.

Sorry for this paragraph, because I have changed commit message for this patch, the range-diff part would be outdated. But the code is still the same. So I hope this will not cause much trouble. And this paragraph should be the following:

And we cannot either report a warn fsck message to the user. This is because if the caller set the "strict" field in "fsck_options", fsck warnings will be converted to errors.

I will fix this in the next version until I receive enough feedback.

Thanks.

#30 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

In reply to shejialuo
Quoted 4 lines · shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using "files-backend.c::parse_loose_ref_contents", we allow the ref content to be end with no newline or contain some garbages.

"to be end with" -> "to end with".
"or contain" -> "or to contain" (optional, I think).

Or "... the ref content without terminating newline, or with extra bytes after the terminating newline."

Quoted 4 lines

It may seem that we should report an error or warn fsck message to the user about above situations. However, there may be some third-party tools customizing the content of refs. We should not report an error fsck message.

Even though we never created such loose refs ourselves, we have accepted such loose refs forever, so it is entirely possible that third-party tools may rely on such loose refs being valid. Let's notice such a "curiously formatted" loose ref files and tell the users our findings, so that we can assess the possible extent of damage if/when we retroactively tightened the parsing rules in the future.

Quoted 2 lines

We should not allow the user to upgrade the fsck warnings to errors. It might cause compatibility issue which will break the legacy repository.

I am not sure this is a right thing to say. If the user wants to ensure that the tool they use in their repository, which may include some third-party reimplementation of Git, would never create such a (semi-)malformed loose ref files, it is within their right, and it is the most reasonable way, to promote these "curiously formatted loose ref" fsck warnings to errors.

Is your "We should not allow" above backed by code that prevents them from promoting the warnings to errors, or is it merely a declaration of your intention?

Quoted 5 lines

So we add the following two fsck infos to represent the situation where the ref content ends without newline or has garbages:

1. "refMissingNewline(INFO)": A valid ref does not end with newline.
2. "trailingRefContent(INFO)": A ref has trailing contents.

OK.

Quoted 3 lines

In "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN", and we can still warn the user about these situations when using "git-refs verify" without introducing compatibility issue.

OK.

Quoted 4 lines

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

Good.

Documentation/fsck-msgids.txt line 183

Quoted 12 lines
Documentation/fsck-msgids.txt +6 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.

There is no mention of "you shouldn't promote these to error" here, which is good. But wouldn't we want to tell users to report such curiously formatted loose refs, after figuring out who created them, to help us to eventually make the check stricter in the future?

Git 3.0 boundary might be a good time to tighten interoperability rules such that we won't accept anything we wouldn't have written ourselves (not limited to loose ref format, but this applies to anything on-disk or on-wire), but we'd need enough preparation if we want to be able to do so in the future.

Thanks.

#31 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 19 lines · Junio C Hamano

Documentation/fsck-msgids.txt line 183

Quoted 12 lines · shejialuo
Documentation/fsck-msgids.txt +6 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.

There is no mention of "you shouldn't promote these to error" here, which is good. But wouldn't we want to tell users to report such curiously formatted loose refs, after figuring out who created them, to help us to eventually make the check stricter in the future?

Quoted 5 lines

Git 3.0 boundary might be a good time to tighten interoperability rules such that we won't accept anything we wouldn't have written ourselves (not limited to loose ref format, but this applies to anything on-disk or on-wire), but we'd need enough preparation if we want to be able to do so in the future.

I quite like this idea. Jialuo, would you maybe want to include another patch on top that adds a paragraph to Documentation/BreakingChanges.txt? It should note that this is not yet settled and depends on whether or not we see complaints with your new checks.

I guess another prereq for the change is to integrate git refs verify with git-fsck(1), because otherwise people likely wouldn't see the new messages in the first place.

Patrick

#32 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 7 lines · Patrick Steinhardt
Quoted 5 lines

Git 3.0 boundary might be a good time to tighten interoperability rules such that we won't accept anything we wouldn't have written ourselves (not limited to loose ref format, but this applies to anything on-disk or on-wire), but we'd need enough preparation if we want to be able to do so in the future.

I quite like this idea.

I wouldn't say that I wrote it as a devil's advocate comment, but I was hoping that somebody quote Postel in response, as the above advocates a directly opposite position, which I wouldn't usually take.

Quoted 3 lines

I guess another prereq for the change is to integrate git refs verify with git-fsck(1), because otherwise people likely wouldn't see the new messages in the first place.

Absolutely.

#33 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Patrick Steinhardt
Quoted 5 lines

Git 3.0 boundary might be a good time to tighten interoperability rules such that we won't accept anything we wouldn't have written ourselves (not limited to loose ref format, but this applies to anything on-disk or on-wire), but we'd need enough preparation if we want to be able to do so in the future.

Quoted 6 lines
Quoted 1 line

I quite like this idea.

I wouldn't say that I wrote it as a devil's advocate comment, but I was hoping that somebody quote Postel in response, as the above advocates a directly opposite position, which I wouldn't usually take.

For context, this is the quote you probably refer to: "be conservative in what you do, be liberal in what you accept from others".

In any case, I still think it is sensible to at least warn about refs like this. It is unexpected to me and may indicate real issues in the understanding of others that end up writing to the refdb. If there are implementations of Git out there that intentionally use our lax parsing to e.g. stuff additional metadata into refs, then we need to tell them that this is not okay.

This may have been fine in the past where there was only a single ref backend, but now with multiple ref backends the picture has changed in my opinion.

Patrick

author
#34 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using "files-backend.c::parse_loose_ref_contents", we allow the ref content to be end with no newline or contain some garbages.

Quoted 6 lines

"to be end with" -> "to end with".
"or contain" -> "or to contain" (optional, I think).

Or "... the ref content without terminating newline, or with extra bytes after the terminating newline."

Thanks, I will fix this in the next version.

Quoted 5 lines
Quoted 4 lines

It may seem that we should report an error or warn fsck message to the user about above situations. However, there may be some third-party tools customizing the content of refs. We should not report an error fsck message.

Quoted 8 lines

Even though we never created such loose refs ourselves, we have accepted such loose refs forever, so it is entirely possible that third-party tools may rely on such loose refs being valid. Let's notice such a "curiously formatted" loose ref files and tell the users our findings, so that we can assess the possible extent of damage if/when we retroactively tightened the parsing rules in the future.

I think I could organize the above to the commit message to better show the motivation why we should not report an error fsck message.

Quoted 10 lines
Quoted 2 lines

We should not allow the user to upgrade the fsck warnings to errors. It might cause compatibility issue which will break the legacy repository.

I am not sure this is a right thing to say. If the user wants to ensure that the tool they use in their repository, which may include some third-party reimplementation of Git, would never create such a (semi-)malformed loose ref files, it is within their right, and it is the most reasonable way, to promote these "curiously formatted loose ref" fsck warnings to errors.

Quoted 4 lines

Is your "We should not allow" above backed by code that prevents them from promoting the warnings to errors, or is it merely a declaration of your intention?

I have introduced some misunderstanding here. In the previous paragraph, I have mentioned that if the caller set the "strict" field in "fsck_options", the fsck warns would be automatically converted to fsck errors which may cause some trouble.

So I think here we should move this paragraph just after the previous paragraph to indicate why we do want to make a info fsck message here. Actually, the user could still explicitly use the following command

git -c fsck.refMissingNewline=error refs verify

to upgrade the fsck info to fsck error. But if the user use "--strict" like the following:

  git refs verify --strict

The fsck warns would be automatically converted to fsck errors. But actually at current, we do not want to the user implicitly upgrade fsck warns to fsck errors by using "--strict" flag. That's why we need to introduce the "FSCK_INO" here.

Actually, I was inspired by the Jeff King's commit:

4dd3b045f5 (fsck: downgrade tree badFilemode to "info", 2022-08-10)

In this commit, Jeff downgrades badFilemode to "info" to avoid above situation. I will improve the commit message to make things clearer.

However, from my perspective, the semantic of "FSCK_INFO" is a little unsuitable here. The comment says:

/* infos (reported as warnings, but ignored by default) */

The "ignored by default" here is very confusing. Actually, we make the "info" lower than the "warn" to avoid automatically converting the "warn" to "error" by setting "strict" field in "fsck_options".

But "ignored by default" will make the user think "oh, it's info, but we report it as warnings". We cannot know the real intention of the "FSCK_INFO" unless we have above context.

But I guess this is too far from the intention of this patch. We may improve this later.

Quoted 4 lines
Quoted 4 lines
Documentation/fsck-msgids.txt +0 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
Quoted 14 lines

Documentation/fsck-msgids.txt line 183

Quoted 8 lines
Documentation/fsck-msgids.txt +6 -0
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.

There is no mention of "you shouldn't promote these to error" here, which is good. But wouldn't we want to tell users to report such curiously formatted loose refs, after figuring out who created them, to help us to eventually make the check stricter in the future?

From the review from the Patrick, I will add another patch in the "Documentation/BreakingChanges.txt" later.

Quoted 8 lines

Git 3.0 boundary might be a good time to tighten interoperability rules such that we won't accept anything we wouldn't have written ourselves (not limited to loose ref format, but this applies to anything on-disk or on-wire), but we'd need enough preparation if we want to be able to do so in the future.

Thanks.

Thanks,
Jialuo

#35 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 18 lines · shejialuo
Quoted 4 lines
Quoted 4 lines
Documentation/fsck-msgids.txt +0 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
Quoted 13 lines

Documentation/fsck-msgids.txt line 183

Quoted 8 lines
Documentation/fsck-msgids.txt +6 -0
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.

There is no mention of "you shouldn't promote these to error" here, which is good. But wouldn't we want to tell users to report such curiously formatted loose refs, after figuring out who created them, to help us to eventually make the check stricter in the future?

Quoted 2 lines

From the review from the Patrick, I will add another patch in the "Documentation/BreakingChanges.txt" later.

As that documentation is not end-user facing, it is orthogonal and unrelated.

What I meant was that we need to tell the user that the refs they have (and the third-party tools they used to create them) may be declared invalid in a future version of Git and they would want to report it, in order to influence our possible future direction. And we need to do so in an end-user facing documentation (i.e. the part of the patch quoted above) and/or in the info messages themselves.

#36 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

In reply to shejialuo

Documentation/fsck-msgids.txt line 177

Quoted 6 lines · shejialuo
Documentation/fsck-msgids.txt +2 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.

This reads a bit funny to me. If the ref is valid, why do we complain?

Maybe this would read better if you said "An otherwise valid ref does not end with a newline".

Quoted 13 lines
refs/files-backend.c +9 -0
@@ -3430,6 +3434,65 @@ 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};

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3455

Quoted 10 lines
refs/files-backend.c +10 -0
+	const char *trailing = NULL;
+	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_ISREG(iter->st.st_mode)) {

This is still indenting the whole body. You mentioned that you don't want to use goto, but in our codebase it's actually quite idiomatic. And you already use it anyway.

Quoted 11 lines
t/t0602-reffiles-fsck.sh +3 -0
index 71a4d1a5ae..7c1910d784 100755
@@ -89,4 +89,91 @@ test_expect_success 'ref name check should be adapted into fsck messages' '
 	test_must_be_empty err
 '
 
+test_expect_success 'regular ref content should be checked' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 104

Quoted 10 lines
t/t0602-reffiles-fsck.sh +10 -0
+	branch_dir_prefix=.git/refs/heads &&
+	tag_dir_prefix=.git/refs/tags &&
+	cd repo &&
+	git commit --allow-empty -m initial &&
+	git checkout -b branch-1 &&
+	git tag tag-1 &&
+	git commit --allow-empty -m second &&
+	git checkout -b branch-2 &&
+	git tag tag-2 &&
+	git checkout -b a/b/tag-2 &&

Wouldn't it be sufficient to only create a single commit, e.g. via test_commit? From all I can see all you need is some object ID, so creating the tags and second commit doesn't seem to be necessary.

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 106

Quoted 1 line
t/t0602-reffiles-fsck.sh +1 -0
+	printf "%s" "$(git rev-parse branch-1)" > $branch_dir_prefix/branch-1-no-newline &&

We don't typically have spaces after the redirect. So you should remove them here and in all the subsequent instances.

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 112

Quoted 6 lines
t/t0602-reffiles-fsck.sh +6 -0
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/heads/branch-1-no-newline: refMissingNewline: missing newline
+	EOF
+	rm $branch_dir_prefix/branch-1-no-newline &&
+	test_cmp expect err &&

I was wondering whether each of these cases should be a separate test, but that may be a bit wasteful. Alternatively, can we maybe set up a single repository with all the garbage that we want to verify and then double check that executing git refs verify surfaces them all in a single invocation?

Patrick

author
#37 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

Quoted 5 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo
Documentation/fsck-msgids.txt +0 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
Quoted 8 lines

Documentation/fsck-msgids.txt line 177

Quoted 2 lines
Documentation/fsck-msgids.txt +2 -0
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.

This reads a bit funny to me. If the ref is valid, why do we complain?

Maybe this would read better if you said "An otherwise valid ref does not end with a newline".

I think we should just drop the "valid" here. Because for symref, it may miss newline and is NOT valid.

I will improve this in the next version.

Quoted 19 lines
Quoted 19 lines
refs/files-backend.c +15 -0
@@ -3430,6 +3434,65 @@ 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};
+	const char *trailing = NULL;
+	unsigned int type = 0;
+	int failure_errno = 0;
+	struct object_id oid;
+	int ret = 0;
+
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3455

Quoted 4 lines
refs/files-backend.c +4 -0
+	strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
+	report.path = refname.buf;
+
+	if (S_ISREG(iter->st.st_mode)) {

This is still indenting the whole body. You mentioned that you don't want to use goto, but in our codebase it's actually quite idiomatic. And you already use it anyway.

I think so, indenting is noisy. Will use "goto" to avoid indenting.

Quoted 17 lines
Quoted 17 lines
t/t0602-reffiles-fsck.sh +9 -0
index 71a4d1a5ae..7c1910d784 100755
@@ -89,4 +89,91 @@ test_expect_success 'ref name check should be adapted into fsck messages' '
 	test_must_be_empty err
 '
 
+test_expect_success 'regular ref content should be checked' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	branch_dir_prefix=.git/refs/heads &&
+	tag_dir_prefix=.git/refs/tags &&
+	cd repo &&
+	git commit --allow-empty -m initial &&
+	git checkout -b branch-1 &&
+	git tag tag-1 &&
Quoted 9 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 104

Quoted 4 lines
t/t0602-reffiles-fsck.sh +4 -0
+	git commit --allow-empty -m second &&
+	git checkout -b branch-2 &&
+	git tag tag-2 &&
+	git checkout -b a/b/tag-2 &&

Wouldn't it be sufficient to only create a single commit, e.g. via test_commit? From all I can see all you need is some object ID, so creating the tags and second commit doesn't seem to be necessary.

I agree with this. I will clean the code for the next version.

Quoted 5 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 106

Quoted 1 line
t/t0602-reffiles-fsck.sh +1 -0
+	printf "%s" "$(git rev-parse branch-1)" > $branch_dir_prefix/branch-1-no-newline &&

We don't typically have spaces after the redirect. So you should remove them here and in all the subsequent instances.

I will clean the code style here.

Quoted 4 lines
Quoted 4 lines
t/t0602-reffiles-fsck.sh +4 -0
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/heads/branch-1-no-newline: refMissingNewline: missing newline
+	EOF
Quoted 9 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 111

Quoted 2 lines
t/t0602-reffiles-fsck.sh +2 -0
+	rm $branch_dir_prefix/branch-1-no-newline &&
+	test_cmp expect err &&

I was wondering whether each of these cases should be a separate test, but that may be a bit wasteful. Alternatively, can we maybe set up a single repository with all the garbage that we want to verify and then double check that executing git refs verify surfaces them all in a single invocation?

Actually, I have also thought about separating the tests which may clear and I dropped this idea due to the reason the same as yours. I DO agree that we should set up a single repository with all the garbage that we want to verify. This is necessary.

Thanks,
Jialuo

#38 lore raw

Re: [PATCH v2 2/4] ref: add regular ref content check for files backend

In reply to Patrick Steinhardt
Quoted 9 lines · Patrick Steinhardt

Documentation/fsck-msgids.txt line 177

Quoted 6 lines · shejialuo
Documentation/fsck-msgids.txt +2 -0
@@ -170,6 +173,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A valid ref does not end with newline.

This reads a bit funny to me. If the ref is valid, why do we complain?

I think you understood after reading the series through and responded to my "curiously formatted" comment. I understand that these marked as INFO are not about "to complain" but are for us to ask the user to report so that we can learn of any third-party tools that may get in our way to later tighten the parsing rules retroactively.

Quoted 2 lines

Maybe this would read better if you said "An otherwise valid ref does not end with a newline".

So I do agree that the text above is less than optimal. It is "this is valid, but something we wouldn't have written. Who creates such a ref?"

author
#39 lore raw

PATCH v2 3/4 ref: add symbolic ref content check for files backend

In reply to shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any non-null trailing garbage. The following are bad symbolic contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will use "strrchr" to check whether we have newline in the ref content. Then we will check the name of the "pointee" is correct by using "check_refname_format". If the function fails, we need to trim the "pointee" to see whether the null-garbage causes the function fails. If so, we need to report that there is null-garbage in the symref content. Otherwise, we should report the user the "pointee" is bad.

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 77 +++++++++++++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 54 ++++++++++++++++++++++++
 4 files changed, 135 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index fc074fc571..85fd058c81 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefPointee`::
+	(ERROR) The pointee of a symref is bad.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index b85072df57..cbe837f84c 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_POINTEE, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +77 -0

100 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +54 -0

58 lines not on this page · raw message

Signature · 1 line
2.46.0
#40 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

Quoted 3 lines · shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

Just in case you need it in the future, if you ever need to refer to a symbolic ref in a way that it is clear which of the two kinds you are talking about, you can say "textual symref" (a regular file whose contents is "ref: " followed by the target), to contrast them with "symbolic link used as symref".

In the proposed log message of this commit, all references to "symbolic ref" talk about textual ones, so I do not see any need to be extra explicit by saying "textual symref".

Quoted 3 lines

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

Hmph, as the pointee must be within the usual places that you would find refs (either in refs/ directory or pseudo ref files immediately below $GIT_DIR), wouldn't we check the pointee when fsck (or "git refs verify") run and check everything? The pointee will have its turn to be checked, and I am not sure why you need to check the pointee when you find a symbolic ref is pointing at it, which will lead for it to be checked twice (or more).

I however did not find an additional code to "check the pointee itself" in the patch, so perhaps it is OK---the only thing that needs fixing may be the above paragraph if that is the case.

Quoted 11 lines

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any non-null trailing garbage.

Your use of word "null" is probably too confusing to contributors to this project. None of the above has NUL bytes in them. I think you want to say something like this:

A regular file is accepted as a textual symbolic ref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname (e.g. "refs/heads/master", "refs/tags/v1.0"), followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the full refname, but third-party reimplementations of Git may have taken advantage of the looser syntax that is allowed as above.

Quoted 8 lines

The following are bad
symbolic contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will use "strrchr" to check whether we have newline in the ref content.

strrchr() to look for only LF is overly strict. You need to match what refs/files-backend.c:read_ref_internal() does to the contents read from such a loose ref file, i.e. strbuf_rtrim(). Any isspace() bytes are trimmed at the end, including SP, HT, CR and LF.

Quoted 14 lines
refs/files-backend.c +14 -0
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    const char *refname,
+				    struct strbuf *pointee_name,
+				    struct strbuf *pointee_path)
+{
+	const char *newline_pos = NULL;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3465

Quoted 10 lines
refs/files-backend.c +10 -0
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	newline_pos = strrchr(p, '\n');
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");

If newline_pos is NULL, it is truly a "missing newline" situation. If I am reading the code correctly, the severity level is set to INFO, which is good.

If newline_pos is not NULL but newline_pos[1] is not NUL, however, that is not a "missing newline". "refs: refs/heads/master\n " would trigger this report, for example.

As far as I can tell, such a textual symbolic ref is taken as a valid symbolic ref pointing at "refs/heads/master" by refs/files-backend.c:read_ref_internal(), so we are trying to detect a valid but curiously formatted textual symbolic ref file with the above code?

And strrchr() to find the last LF is not sufficient for that purpose. We would never write "refs: refs/head/master \n", but the above code will find the LF, be satisified that the LF is followed by NUL, without realizing that SP there is not something we would have written!

I am not sure if that is worth detecting that if it is something we would have written, but if that were the case, then you would probably need to do

(1) check the last byte of pointee_name.buf[] to make sure that it is LF; and
(2) remember pointee_name.len, run strbuf_rtrim() on pointee_name, and that LF at the end was the only thing that was trimmed by checking the pointee_name.len after trimming.

or something like that. Then you do not have to have an ugly "oh we need to check again"---the production code would not do that, either.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3479

Quoted 12 lines
refs/files-backend.c +12 -0
+	if (check_refname_format(pointee_name->buf, 0)) {
+		/*
+		 * When containing null-garbage, "check_refname_format" will
+		 * fail, we should trim the "pointee" to check again.
+		 */
+		strbuf_rtrim(pointee_name);
+		if (!check_refname_format(pointee_name->buf, 0)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing null-garbage");
+			goto out;
+		}

IOW, the above "let's retry" feels totally wrong. You shouldn't have to do so, and that comes from running check_refname_format() before rtrimming the pointee_name.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3484

Quoted 4 lines
refs/files-backend.c +4 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to refname with invalid format");
+	}

Good. With this check, we know that the referent, if exists, is well-formed. The contents of the referent will then be checked just like all other refs that may not be pointed by any symbolic ref.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3493

Quoted 8 lines
refs/files-backend.c +8 -0
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */
+	if (lstat(pointee_path->buf, &st) < 0)
+		goto out;

Good.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3499

Quoted 5 lines
refs/files-backend.c +5 -0
+	if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to an invalid file type");
+		goto out;

I do not think it is wrong per se, but I am not sure if this check is needed, either. When "git fsck" or "git refs verify" is told to check the loose refs, wouldn't it walk the refs directory and report such an unusual filesystem entity that is not a regular file, symbolic link, or a directory as "there is unusual cruft exist here"?

author
#41 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

Quoted 14 lines · Junio C Hamano
Quoted 3 lines · shejialuo

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

Hmph, as the pointee must be within the usual places that you would find refs (either in refs/ directory or pseudo ref files immediately below $GIT_DIR), wouldn't we check the pointee when fsck (or "git refs verify") run and check everything? The pointee will have its turn to be checked, and I am not sure why you need to check the pointee when you find a symbolic ref is pointing at it, which will lead for it to be checked twice (or more).

Quoted 4 lines

I however did not find an additional code to "check the pointee itself" in the patch, so perhaps it is OK---the only thing that needs fixing may be the above paragraph if that is the case.

Yes, "we will check the 'pointee'" itself makes the reader confused. I will fix the above paragraph. Actually we do not check the "pointee", but check the symref content. Will fix this in the next version.

Quoted 16 lines
Quoted 11 lines

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any non-null trailing garbage.

Your use of word "null" is probably too confusing to contributors to this project. None of the above has NUL bytes in them. I think you want to say something like this:

Quoted 8 lines

A regular file is accepted as a textual symbolic ref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname (e.g. "refs/heads/master", "refs/tags/v1.0"), followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the full refname, but third-party reimplementations of Git may have taken advantage of the looser syntax that is allowed as above.

Thanks for your suggestion. I will improve this in the next version.

Quoted 6 lines
Quoted 6 lines

The following are bad
symbolic contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

Quoted 8 lines
Quoted 2 lines

In order to provide above checks, we will use "strrchr" to check whether we have newline in the ref content.

strrchr() to look for only LF is overly strict. You need to match what refs/files-backend.c:read_ref_internal() does to the contents read from such a loose ref file, i.e. strbuf_rtrim(). Any isspace() bytes are trimmed at the end, including SP, HT, CR and LF.

I will look into how "strbuf_rtrim" does to see whether we can reuse some functions to avoid repetition.

Quoted 29 lines
Quoted 14 lines
refs/files-backend.c +14 -0
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    const char *refname,
+				    struct strbuf *pointee_name,
+				    struct strbuf *pointee_path)
+{
+	const char *newline_pos = NULL;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3465

Quoted 10 lines
refs/files-backend.c +10 -0
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	newline_pos = strrchr(p, '\n');
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");

If newline_pos is NULL, it is truly a "missing newline" situation. If I am reading the code correctly, the severity level is set to INFO, which is good.

Quoted 4 lines

If newline_pos is not NULL but newline_pos[1] is not NUL, however, that is not a "missing newline". "refs: refs/heads/master\n " would trigger this report, for example.

When I design this, I actually consider "ref: refs/heads/master\n " is still missing the newline. And then we also report that it has garbage. I think "ref: refs/heads/master\n \n" is not missing the newline. But, I don't think this is good.

I will find a good way to handle this.

Quoted 5 lines

As far as I can tell, such a textual symbolic ref is taken as a valid symbolic ref pointing at "refs/heads/master" by refs/files-backend.c:read_ref_internal(), so we are trying to detect a valid but curiously formatted textual symbolic ref file with the above code?

Yes, these situations will be taken as a valid symbolic ref but actually there are something wrong. So this is what we need to care about.

Quoted 6 lines

And strrchr() to find the last LF is not sufficient for that purpose. We would never write "refs: refs/head/master \n", but the above code will find the LF, be satisified that the LF is followed by NUL, without realizing that SP there is not something we would have written!

I totally ignored this situation, and in current patch, we cannot check this. I know why Patrick lets me use "strchr" but not "strrchr". I think we should find the last '\n'. But instead we need to find the first '\n'. However, in this example, we will still fail by using "strchr". This part should be totally re-designed.

Quoted 5 lines

I am not sure if that is worth detecting that if it is something we would have written, but if that were the case, then you would probably need to do

Quoted 9 lines

(1) check the last byte of pointee_name.buf[] to make sure that it is LF; and
(2) remember pointee_name.len, run strbuf_rtrim() on pointee_name, and that LF at the end was the only thing that was trimmed by checking the pointee_name.len after trimming.

or something like that. Then you do not have to have an ugly "oh we need to check again"---the production code would not do that, either.

Yes, this is a good idea.

Quoted 8 lines
Quoted 8 lines
refs/files-backend.c +8 -0
+	if (check_refname_format(pointee_name->buf, 0)) {
+		/*
+		 * When containing null-garbage, "check_refname_format" will
+		 * fail, we should trim the "pointee" to check again.
+		 */
+		strbuf_rtrim(pointee_name);
+		if (!check_refname_format(pointee_name->buf, 0)) {
+			ret = fsck_report_ref(o, report,
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3479

Quoted 4 lines
refs/files-backend.c +4 -0
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing null-garbage");
+			goto out;
+		}

IOW, the above "let's retry" feels totally wrong. You shouldn't have to do so, and that comes from running check_refname_format() before rtrimming the pointee_name.

Yes, actually, I have thought I could compare the length change after executing the "strbuf_rtrim". I don't want to create two new variables, so I call "check_refname_format" twice.

Will fix this in the next version.

Quoted 4 lines
Quoted 4 lines
refs/files-backend.c +4 -0
+	if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "points to an invalid file type");
Quoted 8 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+		goto out;

I do not think it is wrong per se, but I am not sure if this check is needed, either. When "git fsck" or "git refs verify" is told to check the loose refs, wouldn't it walk the refs directory and report such an unusual filesystem entity that is not a regular file, symbolic link, or a directory as "there is unusual cruft exist here"?

When setting up the infrastructure, actually we DO report filesystem entity that is not a regular file or symbolic link like the following:

    if (S_ISDIR(iter->st.st_mode)) {
        continue;
    } else if (S_ISREG(iter->st.st_mode) ||
               S_ISLNK(iter->st.st_mode)) {
        ...;
    } else {
      // report file system error
    }

We do not check the directory, because the directory will be always valid in the filesystem. we could not say that

"refs/heads/a/" is a bad ref.

So, this check mainly need to check whether the symref points to a directory. Actually, Patrick has also gave the review about this question in the previous version:

Quoted 3 lines

What exactly are we guarding against here? Don't we already verify that files in refs/ have the correct type? Or are we checking that it does not point to a directory?

However, we should remove this line, because "check_refname_format" will take care for us.

git check-ref-format 'refs/heads/'

It will generate an error. So, we could entirely remove this line and let "check_refname_format" do this. And we could also remove the

    if (lstat(pointee_path->buf, &st) < 0)
        goto out;

The code will be much more clean.

Thanks,
Jialuo

#42 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

In reply to shejialuo
Quoted 14 lines · shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

Quoted 3 lines

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

Now that we're talking about tightening the rules for direct refs, I wonder whether we'd also want to apply the same rules to symrefs. Namely, when there is trailing whitespace we should generate an INFO-level message about that, too. This is mostly for the sake of consistency.

[snip]

Documentation/fsck-msgids.txt line 32

Quoted 10 lines
Documentation/fsck-msgids.txt +2 -0
index fc074fc571..85fd058c81 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefPointee`::
+	(ERROR) The pointee of a symref is bad.

I think we'd want to clarify what "bad" is supposed to mean. Like, is a missing symref pointee bad? If this is about the format of the pointee's name, we might want to call this "badSymrefPointeeName".

Also, I think we don't typically call the value of a symbolic ref "pointee", but "target". Searching for "pointee" in our codebase only gives a single hit, and that one is not related to symbolic refs.

Quoted 40 lines
fsck.h +1 -0
index b85072df57..cbe837f84c 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_POINTEE, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +20 -0
index 69c00073eb..382c73fcf7 100644
@@ -3434,11 +3434,81 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  const char *refs_check_dir,
 				  struct dir_iterator *iter);
 
+/*
+ * Check the symref "pointee_name" and "pointee_path". The caller should
+ * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
+ * would be the content after "refs:".
+ */
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    const char *refname,
+				    struct strbuf *pointee_name,
+				    struct strbuf *pointee_path)
+{
+	const char *newline_pos = NULL;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3466

Quoted 10 lines
refs/files-backend.c +10 -0
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	newline_pos = strrchr(p, '\n');
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+	}

The second condition *(newline_pos + 1) checks whether there is any data after the newline, doesn't it? That indicates a different kind of error than "missing newline", namely that there is trailing garbage. I guess we'd want to report a separate info-level message for this.

Also, shouldn't we use strchr instead of strrchr()? Otherwise, we're only checking for trailing garbage after the _last_ newline, not after the first one.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3479

Quoted 12 lines
refs/files-backend.c +12 -0
+	if (check_refname_format(pointee_name->buf, 0)) {
+		/*
+		 * When containing null-garbage, "check_refname_format" will
+		 * fail, we should trim the "pointee" to check again.
+		 */
+		strbuf_rtrim(pointee_name);
+		if (!check_refname_format(pointee_name->buf, 0)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing null-garbage");
+			goto out;
+		}

Ah, I didn't get at first that we're doing the check a second time here. As mentioned above, I think we should check for trailing garbage further up already and more explicitly.

Quoted 16 lines
t/t0602-reffiles-fsck.sh +8 -0
index 7c1910d784..69280795ca 100755
@@ -176,4 +176,58 @@ test_expect_success 'regular ref content should be checked' '
 	test_cmp expect err
 '
 
+test_expect_success 'symbolic ref content should be checked' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	branch_dir_prefix=.git/refs/heads &&
+	tag_dir_prefix=.git/refs/tags &&
+	cd repo &&
+	git commit --allow-empty -m initial &&
+	git checkout -b branch-1 &&

t/t0602-reffiles-fsck.sh test_expect_success 'regular ref content should be checked' ' · line 196

Quoted 10 lines
t/t0602-reffiles-fsck.sh +10 -0
+	git tag tag-1 &&
+	git checkout -b a/b/branch-2 &&
+
+	printf "ref: refs/heads/branch" > $branch_dir_prefix/branch-1-no-newline &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/heads/branch-1-no-newline: refMissingNewline: missing newline
+	EOF
+	rm $branch_dir_prefix/branch-1-no-newline &&
+	test_cmp expect err &&

Same comments here as in the preceding patch for the tests.

Patrick

author
#43 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

Quoted 19 lines · Patrick Steinhardt
Quoted 14 lines · shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symbolic ref points to. Instead, we just check the content of the symbolic ref itself.

In order to check the content of the symbolic ref, create a function "files_fsck_symref_target". It will first check whether the "pointee" is under the "refs/" directory and then we will check the "pointee" itself.

There is no specification about the content of the symbolic ref. Although we do write "ref: %s\n" to create a symbolic ref by using "git-symbolic-ref(1)" command. However, this is not mandatory. We still accept symbolic refs with null trailing garbage. Put it more specific, the following are correct:

Quoted 3 lines

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

Quoted 6 lines

Now that we're talking about tightening the rules for direct refs, I wonder whether we'd also want to apply the same rules to symrefs. Namely, when there is trailing whitespace we should generate an INFO-level message about that, too. This is mostly for the sake of consistency.

Yes, actually this patch does this. I think I need to mention we reuse the "FSCK_INFO" message id defined in the [PATCH v2 2/4].

Quoted 9 lines

[snip]

Quoted 8 lines
Documentation/fsck-msgids.txt +0 -0
index fc074fc571..85fd058c81 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
Quoted 7 lines

Documentation/fsck-msgids.txt line 32

Quoted 2 lines
Documentation/fsck-msgids.txt +2 -0
+`badSymrefPointee`::
+	(ERROR) The pointee of a symref is bad.

I think we'd want to clarify what "bad" is supposed to mean. Like, is a missing symref pointee bad? If this is about the format of the pointee's name, we might want to call this "badSymrefPointeeName".

I agree, bad is too general here, we need to make it concrete.

Quoted 4 lines

Also, I think we don't typically call the value of a symbolic ref "pointee", but "target". Searching for "pointee" in our codebase only gives a single hit, and that one is not related to symbolic refs.

Thanks, I will fix this in the next version.

Quoted 36 lines
Quoted 20 lines
Documentation/fsck-msgids.txt +20 -0
+/*
+ * Check the symref "pointee_name" and "pointee_path". The caller should
+ * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
+ * would be the content after "refs:".
+ */
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    const char *refname,
+				    struct strbuf *pointee_name,
+				    struct strbuf *pointee_path)
+{
+	const char *newline_pos = NULL;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,

Documentation/fsck-msgids.txt

Quoted 10 lines
Documentation/fsck-msgids.txt +10 -0
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	newline_pos = strrchr(p, '\n');
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+	}

The second condition *(newline_pos + 1) checks whether there is any data after the newline, doesn't it? That indicates a different kind of error than "missing newline", namely that there is trailing garbage. I guess we'd want to report a separate info-level message for this.

Quoted 4 lines

Also, shouldn't we use strchr instead of strrchr()? Otherwise, we're only checking for trailing garbage after the _last_ newline, not after the first one.

Yes, I totally made a mistake here. I will try to think about a new design. I have already replied to Junio like the following:

Quoted 12 lines
Quoted 5 lines

And strrchr() to find the last LF is not sufficient for that purpose. We would never write "refs: refs/head/master \n", but the above code will find the LF, be satisified that the LF is followed by NUL, without realizing that SP there is not something we would have written!

I totally ignored this situation, and in current patch, we cannot check this. I know why Patrick lets me use "strchr" but not "strrchr". I think we should find the last '\n'. But instead we need to find the first '\n'. However, in this example, we will still fail by using "strchr". This part should be totally re-designed.

Quoted 17 lines

Documentation/fsck-msgids.txt

Quoted 12 lines
Documentation/fsck-msgids.txt +12 -0
+	if (check_refname_format(pointee_name->buf, 0)) {
+		/*
+		 * When containing null-garbage, "check_refname_format" will
+		 * fail, we should trim the "pointee" to check again.
+		 */
+		strbuf_rtrim(pointee_name);
+		if (!check_refname_format(pointee_name->buf, 0)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing null-garbage");
+			goto out;
+		}

Ah, I didn't get at first that we're doing the check a second time here. As mentioned above, I think we should check for trailing garbage further up already and more explicitly.

Well, I guess the implementation about this is totally wrong, which will make the reviewers hard to understand. I will drop this way to explicitly check the garbage.

Thanks,
Jialuo

#44 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

In reply to Patrick Steinhardt
Quoted 3 lines · Patrick Steinhardt

Also, I think we don't typically call the value of a symbolic ref "pointee", but "target". Searching for "pointee" in our codebase only gives a single hit, and that one is not related to symbolic refs.

Yesterday while I was studying for reviewing this series, I saw some existing code that call them "referent". There may also be "target".

Quoted 11 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3466

Quoted 5 lines
refs/files-backend.c +5 -0
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+	}

The second condition *(newline_pos + 1) checks whether there is any data after the newline, doesn't it? That indicates a different kind of error than "missing newline", namely that there is trailing garbage. I guess we'd want to report a separate info-level message for this.

Quoted 3 lines

Also, shouldn't we use strchr instead of strrchr()? Otherwise, we're only checking for trailing garbage after the _last_ newline, not after the first one.

None of the above. It should strbuf_rtrim() and if we removed anything but just a single terminating LF, we are looking at something we wouldn't ahve written. The next check_refname_format() call would then find "trailing garbage".

- "refs/heads/master \n " gets rtrimmed to "refs/heads/master", which is "valid but curious".

- "refs/heads/main trash\n " becomes "refs/heads/main trash", which is outright bad.

#45 lore raw

Re: [PATCH v2 3/4] ref: add symbolic ref content check for files backend

Quoted 8 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

Also, I think we don't typically call the value of a symbolic ref "pointee", but "target". Searching for "pointee" in our codebase only gives a single hit, and that one is not related to symbolic refs.

Yesterday while I was studying for reviewing this series, I saw some existing code that call them "referent". There may also be "target".

Ah, true, I totally forgot about "referent". I guess we use both, but it would of course be great if we only had a single term to refer them. Referent seems to be used more widely, at least in the refs subsystem.

Quoted 11 lines
Quoted 11 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3466

Quoted 5 lines
refs/files-backend.c +5 -0
+	if (!newline_pos || *(newline_pos + 1)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+	}

The second condition *(newline_pos + 1) checks whether there is any data after the newline, doesn't it? That indicates a different kind of error than "missing newline", namely that there is trailing garbage. I guess we'd want to report a separate info-level message for this.

Quoted 8 lines
Quoted 3 lines

Also, shouldn't we use strchr instead of strrchr()? Otherwise, we're only checking for trailing garbage after the _last_ newline, not after the first one.

None of the above. It should strbuf_rtrim() and if we removed anything but just a single terminating LF, we are looking at something we wouldn't ahve written. The next check_refname_format() call would then find "trailing garbage".

Fair.

Quoted 2 lines

- "refs/heads/master \n " gets rtrimmed to "refs/heads/master", which is "valid but curious".

Okay. This _may_ be something to generate an info message for, mostly in the same spirit as we want to do it for direct refs.

Quoted 2 lines

- "refs/heads/main trash\n " becomes "refs/heads/main trash", which is outright bad.

Yeah, this one should be an error indeed.

Patrick

author
#46 lore raw

PATCH v2 4/4 ref: add symlink ref check for files backend

In reply to shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which are legacy symbolic links. We should not check the trailing garbage for symbolic links. Add a new parameter "symbolic_link" to disable some checks which should only be used for symbolic ref.

We firstly use the "strbuf_add_real_path" to resolve the symlinks and get the absolute path "pointee_path" which the symlink ref points to. Then we can get the absolute path "abs_gitdir" of the "gitdir". By combining "pointee_path" and "abs_gitdir", we can extract the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

---
 refs/files-backend.c     | 68 +++++++++++++++++++++++++++++-----------
 t/t0602-reffiles-fsck.sh | 44 ++++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 18 deletions(-)
refs/files-backend.c +50 -18
index 382c73fcf7..8641e3ba65 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../abspath.h"
 #include "../copy.h"
 #include "../environment.h"
 #include "../gettext.h"
@@ -3437,13 +3438,15 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 /*
  * Check the symref "pointee_name" and "pointee_path". The caller should
  * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
- * would be the content after "refs:".
+ * would be the content after "refs:". For symblic link, "pointee_name" would
+ * be the relative path agaignst "gitdir".
  */
 static int files_fsck_symref_target(struct fsck_options *o,
 				    struct fsck_ref_report *report,
 				    const char *refname,
 				    struct strbuf *pointee_name,
-				    struct strbuf *pointee_path)
+				    struct strbuf *pointee_path,
+				    unsigned int symbolic_link)
 {
 	const char *newline_pos = NULL;
 	const char *p = NULL;
@@ -3458,24 +3461,28 @@ static int files_fsck_symref_target(struct fsck_options *o,
 		goto out;
 	}
 
-	newline_pos = strrchr(p, '\n');
-	if (!newline_pos || *(newline_pos + 1)) {
-		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_REF_MISSING_NEWLINE,
-				      "missing newline");
+	if (!symbolic_link) {
+		newline_pos = strrchr(p, '\n');
+		if (!newline_pos || *(newline_pos + 1)) {
+			ret = fsck_report_ref(o, report,
+					      FSCK_MSG_REF_MISSING_NEWLINE,
+					      "missing newline");
+		}
 	}
 
 	if (check_refname_format(pointee_name->buf, 0)) {
-		/*
-		 * When containing null-garbage, "check_refname_format" will
-		 * fail, we should trim the "pointee" to check again.
-		 */
-		strbuf_rtrim(pointee_name);
-		if (!check_refname_format(pointee_name->buf, 0)) {
-			ret = fsck_report_ref(o, report,
-					      FSCK_MSG_TRAILING_REF_CONTENT,
-					      "trailing null-garbage");
-			goto out;
+		if (!symbolic_link) {
+			/*
+			* When containing null-garbage, "check_refname_format" will
+			* fail, we should trim the "pointee" to check again.
+			*/
+			strbuf_rtrim(pointee_name);
+			if (!check_refname_format(pointee_name->buf, 0)) {
+				ret = fsck_report_ref(o, report,
+						      FSCK_MSG_TRAILING_REF_CONTENT,
+						      "trailing null-garbage");
+				goto out;
+			}
 		}
 
 		ret = fsck_report_ref(o, report,
@@ -3510,9 +3517,12 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 {
 	struct strbuf pointee_path = STRBUF_INIT;
 	struct strbuf ref_content = STRBUF_INIT;
+	struct strbuf abs_gitdir = STRBUF_INIT;
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
 	struct fsck_ref_report report = {0};
+	const char *pointee_name = NULL;
+	unsigned int symbolic_link = 0;
 	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
@@ -3557,16 +3567,38 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 				    ref_store->gitdir, referent.buf);
 			ret = files_fsck_symref_target(o, &report, refname.buf,
 						       &referent,
-						       &pointee_path);
+						       &pointee_path,
+						       symbolic_link);
 		}
 		goto cleanup;
 	}
 
+	symbolic_link = 1;
+
+	strbuf_add_real_path(&pointee_path, iter->path.buf);
+	strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
+	strbuf_normalize_path(&abs_gitdir);
+	if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
+		strbuf_addch(&abs_gitdir, '/');
+
+	if (!skip_prefix(pointee_path.buf, abs_gitdir.buf, &pointee_name)) {
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_BAD_SYMREF_POINTEE,
+				      "point to target outside gitdir");
+		goto cleanup;
+	}
+
+	strbuf_addstr(&referent, pointee_name);
+	ret = files_fsck_symref_target(o, &report, refname.buf,
+				       &referent, &pointee_path,
+				       symbolic_link);
+
 cleanup:
 	strbuf_release(&refname);
 	strbuf_release(&ref_content);
 	strbuf_release(&referent);
 	strbuf_release(&pointee_path);
+	strbuf_release(&abs_gitdir);
 	return ret;
 }
 
t/t0602-reffiles-fsck.sh +44 -0

48 lines not on this page · raw message

Signature · 1 line
2.46.0
#47 lore raw

PATCH 1/1 SQUASH??? remove unused parameters

In reply to shejialuo

With -Wunused-parameter, the compiler notices that many parameters are unused. They are truly unused, and the signatures for the functions involved are not constrained externally, so we can simply drop the parameters from the definition of these functions and their callers.

Please squash these in when the topic gets rerolled. Thanks.

 refs/files-backend.c | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)
refs/files-backend.c +5 -8
index 8641e3ba65..69dd283c9d 100644
@@ -1966,9 +1966,8 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
 	return ret;
 }
 
-static int create_symref_lock(struct files_ref_store *refs,
-			      struct ref_lock *lock, const char *refname,
-			      const char *target, struct strbuf *err)
+static int create_symref_lock(struct ref_lock *lock, const char *target,
+			      struct strbuf *err)
 {
 	if (!fdopen_lock_file(&lock->lk, "w")) {
 		strbuf_addf(err, "unable to fdopen %s: %s",
@@ -2584,8 +2583,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
 	}
 
 	if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-		if (create_symref_lock(refs, lock, update->refname,
-				       update->new_target, err)) {
+		if (create_symref_lock(lock, update->new_target, err)) {
 			ret = TRANSACTION_GENERIC_ERROR;
 			goto out;
 		}
@@ -3443,7 +3441,6 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
  */
 static int files_fsck_symref_target(struct fsck_options *o,
 				    struct fsck_ref_report *report,
-				    const char *refname,
 				    struct strbuf *pointee_name,
 				    struct strbuf *pointee_path,
 				    unsigned int symbolic_link)
@@ -3565,7 +3562,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		} else {
 			strbuf_addf(&pointee_path, "%s/%s",
 				    ref_store->gitdir, referent.buf);
-			ret = files_fsck_symref_target(o, &report, refname.buf,
+			ret = files_fsck_symref_target(o, &report,
 						       &referent,
 						       &pointee_path,
 						       symbolic_link);
@@ -3589,7 +3586,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	}
 
 	strbuf_addstr(&referent, pointee_name);
-	ret = files_fsck_symref_target(o, &report, refname.buf,
+	ret = files_fsck_symref_target(o, &report,
 				       &referent, &pointee_path,
 				       symbolic_link);
 
Signature · 1 line
2.46.0-563-gaeb9b172ce
#48 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

In reply to shejialuo

Here is another one.

By the way, Peff, do we have MAYBE_UNUSED that can be used in a case like this one? Platforms without symbolic links supported may well define NO_SYMLINK_HEAD, which makes the incoming parameters unused.

static int create_ref_symlink(struct ref_lock *lock, const char *target)

{
	int ret = -1;
#ifndef NO_SYMLINK_HEAD
	char *ref_path = get_locked_file_path(&lock->lk);
	unlink(ref_path);
	ret = symlink(target, ref_path);
	free(ref_path);

	if (ret)
		fprintf(stderr, "no symlink - falling back to symbolic ref\n");
#endif
	return ret;
}

We can of course do the attached, which I'll let shejialuo to squash into an appropriate patch in the series.

Thanks.

 refs/files-backend.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
refs/files-backend.c +5 -2
index 69dd283c9d..110af32788 100644
@@ -1951,10 +1951,13 @@ static int commit_ref_update(struct files_ref_store *refs,
 	return 0;
 }
 
+#ifdef NO_SYMLINK_HEAD
+#define create_ref_symlink(lock, referent) (-1)
+#else
 static int create_ref_symlink(struct ref_lock *lock, const char *target)
 {
 	int ret = -1;
-#ifndef NO_SYMLINK_HEAD
+
 	char *ref_path = get_locked_file_path(&lock->lk);
 	unlink(ref_path);
 	ret = symlink(target, ref_path);
@@ -1962,9 +1965,9 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
 
 	if (ret)
 		fprintf(stderr, "no symlink - falling back to symbolic ref\n");
-#endif
 	return ret;
 }
+#endif
 
 static int create_symref_lock(struct ref_lock *lock, const char *target,
 			      struct strbuf *err)
#49 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

Quoted 3 lines · Junio C Hamano

By the way, Peff, do we have MAYBE_UNUSED that can be used in a case like this one? Platforms without symbolic links supported may well define NO_SYMLINK_HEAD, which makes the incoming parameters unused.

Yes, it would be fine to use MAYBE_UNUSED in a case like this.

The other option, and what I did for a conditional compilation in imap-send.c, is to just mention the variable like:

  /* mark as used to appease -Wunused-parameter with NO_SYMLINK_HEAD */
  (void)lock;
  (void)target;

In retrospect I think MAYBE_UNUSED is probably a little less magical, and I perhaps should have used it there.

In this particular case, though, where there's no actual code in one half of the #ifdef, I think just defining two separate functions is cleaner. I.e., what you did with a macro below, though I'd probably have just used a real function with UNUSED markers.

As an aside, I wonder if we should consider deprecating and eventually dropping support for core.prefersymlinkrefs. I can't think of a reason anybody would want to use it, and of course it makes no sense as we move on to alternate backends like reftables. I sent patches ages ago:

https://lore.kernel.org/git/[email protected]/

but I think it may have just gotten lost in the shuffle, and I've somehow been meaning to re-submit them for 9 years. :-/

-Peff

#50 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

Quoted 4 lines · Jeff King

As an aside, I wonder if we should consider deprecating and eventually dropping support for core.prefersymlinkrefs. I can't think of a reason anybody would want to use it, and of course it makes no sense as we move on to alternate backends like reftables.

Yup. Perhaps add an entry or two to BreakingChanges document?

 Documentation/BreakingChanges.txt | 6 ++++++
 1 file changed, 6 insertions(+)
Documentation/BreakingChanges.txt +6 -0
index 0532bfcf7f..2a85740f3c 100644
@@ -115,6 +115,12 @@ info/grafts as outdated, 2014-03-05) and will be removed.
 +
 Cf. <[email protected]>.
 
+* Support for core.prefersymlinkrefs will be dropped.  Support for
+  existing repositories that use symbolic links to represent a
+  symbolic ref may or may not be dropped.
++
+Cf. <[email protected]>
+
 == Superseded features that will not be deprecated
 
 Some features have gained newer replacements that aim to improve the design in
#51 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

Quoted 17 lines · Junio C Hamano
Quoted 4 lines · Jeff King

As an aside, I wonder if we should consider deprecating and eventually dropping support for core.prefersymlinkrefs. I can't think of a reason anybody would want to use it, and of course it makes no sense as we move on to alternate backends like reftables.

Yup. Perhaps add an entry or two to BreakingChanges document?

 Documentation/BreakingChanges.txt | 6 ++++++
 1 file changed, 6 insertions(+)
Documentation/BreakingChanges.txt +0 -0
index 0532bfcf7f..2a85740f3c 100644

Documentation/BreakingChanges.txt info/grafts as outdated, 2014-03-05) and will be removed. · line 124

Quoted 10 lines
Documentation/BreakingChanges.txt +6 -0
@@ -115,6 +115,12 @@ info/grafts as outdated, 2014-03-05) and will be removed.
 +
 Cf. <[email protected]>.
 
+* Support for core.prefersymlinkrefs will be dropped.  Support for
+  existing repositories that use symbolic links to represent a
+  symbolic ref may or may not be dropped.
++
+Cf. <[email protected]>
+
 == Superseded features that will not be deprecated

Yes, I'm very much in favor of that. As Peff said, I don't see a single reason why it would make sense to use symlinks nowadays. We have also supported the "new" syntax for ages now, and I'd be surprised if there were repos out there using it on purpose.

We should probably do the above together with a new check that starts to warn about symbolic links in "refs/" such that users become aware of this deprecation. We'd have to grow the infrastructure to also scan root refs though, which to the best of my knowledge we don't currently scan.

Patrick

#52 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

Quoted 4 lines · Patrick Steinhardt
Quoted 4 lines
Documentation/BreakingChanges.txt +4 -0
+* Support for core.prefersymlinkrefs will be dropped.  Support for
+  existing repositories that use symbolic links to represent a
+  symbolic ref may or may not be dropped.
++
Quoted 8 lines

Documentation/BreakingChanges.txt info/grafts as outdated, 2014-03-05) and will be removed. · line 124

Quoted 3 lines
Documentation/BreakingChanges.txt +2 -0
+Cf. <[email protected]>
+
 == Superseded features that will not be deprecated

...
We should probably do the above together with a new check that starts to warn about symbolic links in "refs/" such that users become aware of this deprecation. We'd have to grow the infrastructure to also scan root refs though, which to the best of my knowledge we don't currently scan.

Yup, that is why the above suggestion is on _this_ thread that is about the "check for curiously formatted symrefs, in the hope that we can retroactively tighten our checks later" topic.

#53 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

In reply to Patrick Steinhardt
Quoted 21 lines · Patrick Steinhardt
Quoted 5 lines
Documentation/BreakingChanges.txt +0 -0
index 0532bfcf7f..2a85740f3c 100644

Documentation/BreakingChanges.txt info/grafts as outdated, 2014-03-05) and will be removed. · line 124

Quoted 10 lines
Documentation/BreakingChanges.txt +6 -0
@@ -115,6 +115,12 @@ info/grafts as outdated, 2014-03-05) and will be removed.
 +
 Cf. <[email protected]>.
 
+* Support for core.prefersymlinkrefs will be dropped.  Support for
+  existing repositories that use symbolic links to represent a
+  symbolic ref may or may not be dropped.
++
+Cf. <[email protected]>
+
 == Superseded features that will not be deprecated

Yes, I'm very much in favor of that. As Peff said, I don't see a single reason why it would make sense to use symlinks nowadays. We have also supported the "new" syntax for ages now, and I'd be surprised if there were repos out there using it on purpose.

Quoted 4 lines

We should probably do the above together with a new check that starts to warn about symbolic links in "refs/" such that users become aware of this deprecation. We'd have to grow the infrastructure to also scan root refs though, which to the best of my knowledge we don't currently scan.

I think the first step of the proposal (and what I had written in the patches that I linked) was just that we would stop _writing_ symlinks. And there we'd only need to warn people who have that config option set.

Whether to drop the reading side is less clear to me. I think in the long run it is good as a cleanup (and one less source of weird behavior that malicious local repos can trigger). But that decision can be made separately. I think it would be OK to just issue a deprecation warning whenever we actually follow a symlink (because I think we do so manually, since we need to know the target name as part of the resolution process).

-Peff

author
#54 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

In reply to Junio C Hamano
Quoted 19 lines · Junio C Hamano
Quoted 4 lines · Jeff King

As an aside, I wonder if we should consider deprecating and eventually dropping support for core.prefersymlinkrefs. I can't think of a reason anybody would want to use it, and of course it makes no sense as we move on to alternate backends like reftables.

Yup. Perhaps add an entry or two to BreakingChanges document?

 Documentation/BreakingChanges.txt | 6 ++++++
 1 file changed, 6 insertions(+)
Documentation/BreakingChanges.txt +0 -0
index 0532bfcf7f..2a85740f3c 100644
@@ -115,6 +115,12 @@ info/grafts as outdated, 2014-03-05) and will be removed.
 +
 Cf. <[email protected]>.

Documentation/BreakingChanges.txt info/grafts as outdated, 2014-03-05) and will be removed. · line 126

Quoted 10 lines
Documentation/BreakingChanges.txt +6 -0
 
+* Support for core.prefersymlinkrefs will be dropped.  Support for
+  existing repositories that use symbolic links to represent a
+  symbolic ref may or may not be dropped.
++
+Cf. <[email protected]>
+
 == Superseded features that will not be deprecated
 
 Some features have gained newer replacements that aim to improve the design in

From my current understanding, I think I need to rebase two patches provided by your here:

https://lore.kernel.org/git/[email protected]/
https://lore.kernel.org/git/[email protected]/

I think in this patch, we just info the user that we will drop "core.prefersymlinkrefs" later, so I should not concern about this patch and also the [PATCH 8/6].

Thanks,
Jialuo

#55 lore raw

Re: [PATCH v2 0/4] add ref content check for files backend

Quoted 5 lines · shejialuo

From my current understanding, I think I need to rebase two patches provided by your here:

https://lore.kernel.org/git/[email protected]/
https://lore.kernel.org/git/[email protected]/

They are to be squashed into your patch, "suggested edit" for your changes, not "to be rebased". In other words, we do not want to see a patch (from your v2 as-is) to create problems and then another patch (taken from one of these links) applied on top to remedy them. We instead want to see a patch (start from your v2 but with the changes from these links) that does not introduce problems in the first place.

#56 lore raw

[PATCH 8/6] CodingGuidelines: also mention MAYBE_UNUSED

In reply to Jeff King
Quoted 7 lines · Jeff King
Quoted 3 lines · Junio C Hamano

By the way, Peff, do we have MAYBE_UNUSED that can be used in a case like this one? Platforms without symbolic links supported may well define NO_SYMLINK_HEAD, which makes the incoming parameters unused.

Yes, it would be fine to use MAYBE_UNUSED in a case like this.

It turns out that I was, without realizing it myself, making an oblique reference to your patch 7/6 ;-)

Perhaps something along this line?

---- >8 ----
Subject: CodingGuidelines: also mention MAYBE_UNUSED

A function that uses a parameter in one build may lose all uses of the parameter in another build, depending on the configuration. A workaround for such a case, MAYBE_UNUSED, should also be mentioned when we recommend the use of UNUSED to our developers.

---
 Documentation/CodingGuidelines |  5 +++--
 git-compat-util.h              | 21 +++++++++++++++++++++
 2 files changed, 24 insertions(+), 2 deletions(-)
Documentation/CodingGuidelines +3 -2
index d0fc7cfe60..3263245b03 100644
@@ -262,8 +262,9 @@ For C programs:
    like "error: unused parameter 'foo' [-Werror=unused-parameter]",
    which indicates that a function ignores its argument. If the unused
    parameter can't be removed (e.g., because the function is used as a
-   callback and has to match a certain interface), you can annotate the
-   individual parameters with the UNUSED keyword, like "int foo UNUSED".
+   callback and has to match a certain interface), you can annotate
+   the individual parameters with the UNUSED (or MAYBE_UNUSED)
+   keyword, like "int foo UNUSED".
 
  - We try to support a wide range of C compilers to compile Git with,
    including old ones.  As of Git v2.35.0 Git requires C99 (we check
git-compat-util.h +21 -0
index 71b4d23f03..23307ce780 100644
@@ -195,6 +195,17 @@ struct strbuf;
 #define _NETBSD_SOURCE 1
 #define _SGI_SOURCE 1
 
+/*
+ * UNUSED marks a function parameter that is always unused.
+ *
+ * A callback interface may dictate that a function accepts a
+ * parameter at that position, but the implementation of the function
+ * may not need to use the parameter.  In such a case, mark the parameter
+ * with UNUSED.
+ *
+ * When a parameter may be used or unused, depending on conditional
+ * compilation, consider using MAYBE_UNUSED instead.
+ */
 #if GIT_GNUC_PREREQ(4, 5)
 #define UNUSED __attribute__((unused)) \
 	__attribute__((deprecated ("parameter declared as UNUSED")))
@@ -649,6 +660,16 @@ static inline int git_has_dir_sep(const char *path)
 #define RESULT_MUST_BE_USED
 #endif
 
+/*
+ * MAYBE_UNUSED marks a function parameter that may be unused, but
+ * whose use is not an error.
+ *
+ * Depending on a configuration, all uses of a function parameter may
+ * become #ifdef'ed away.  Marking such a parameter with UNUSED would
+ * give a warning in a compilation where the parameter is indeed used,
+ * and not marking such a parameter would give a warning in a
+ * compilation where the parameter is unused.
+ */
 #define MAYBE_UNUSED __attribute__((__unused__))
 
 #include "compat/bswap.h"
#57 lore raw

Re: [PATCH 8/6] CodingGuidelines: also mention MAYBE_UNUSED

Quoted 6 lines · Junio C Hamano
Quoted 1 line

Yes, it would be fine to use MAYBE_UNUSED in a case like this.

It turns out that I was, without realizing it myself, making an oblique reference to your patch 7/6 ;-)

Perhaps something along this line?

Yeah, this looks good. A few small comments below (but I'm not sure anything needs to be changed).

Documentation/CodingGuidelines For C programs: · line 267

Quoted 13 lines
Documentation/CodingGuidelines +3 -2
index d0fc7cfe60..3263245b03 100644
@@ -262,8 +262,9 @@ For C programs:
    like "error: unused parameter 'foo' [-Werror=unused-parameter]",
    which indicates that a function ignores its argument. If the unused
    parameter can't be removed (e.g., because the function is used as a
-   callback and has to match a certain interface), you can annotate the
-   individual parameters with the UNUSED keyword, like "int foo UNUSED".
+   callback and has to match a certain interface), you can annotate
+   the individual parameters with the UNUSED (or MAYBE_UNUSED)
+   keyword, like "int foo UNUSED".

Here I was going to suggest explaining why you'd use one or the other (because I'm afraid of people using MAYBE_UNUSED when UNUSED would be more appropriate). But I think the extra comments you added later are even better, as it lets us explain without cluttering up the CodingGuidelines document.

Documentation/CodingGuidelines For C programs:

Quoted 11 lines
Documentation/CodingGuidelines +11 -0
+/*
+ * UNUSED marks a function parameter that is always unused.
+ *
+ * A callback interface may dictate that a function accepts a
+ * parameter at that position, but the implementation of the function
+ * may not need to use the parameter.  In such a case, mark the parameter
+ * with UNUSED.
+ *
+ * When a parameter may be used or unused, depending on conditional
+ * compilation, consider using MAYBE_UNUSED instead.
+ */

Looks good.

Quoted 11 lines

+/*
+ * MAYBE_UNUSED marks a function parameter that may be unused, but
+ * whose use is not an error.
+ *
+ * Depending on a configuration, all uses of a function parameter may
+ * become #ifdef'ed away. Marking such a parameter with UNUSED would
+ * give a warning in a compilation where the parameter is indeed used,
+ * and not marking such a parameter would give a warning in a
+ * compilation where the parameter is unused.
+ */
#define MAYBE_UNUSED __attribute__((__unused__))

This is all good as pertains to function parameters. But the original reason we added MAYBE_UNUSED was actually for static functions that were auto-generated by the commit-slab macros. Saying "...marks a function parameter" implies to me that it's the only use. I don't know if we want to be more expansive here or not. Adding auto-generated macro functions should be quite a rarity, I'd think.

-Peff

#58 lore raw

Re: [PATCH 8/6] CodingGuidelines: also mention MAYBE_UNUSED

Quoted 12 lines · Jeff King
Quoted 11 lines

+/*
+ * MAYBE_UNUSED marks a function parameter that may be unused, but
+ * whose use is not an error.
+ *
+ * Depending on a configuration, all uses of a function parameter may
+ * become #ifdef'ed away. Marking such a parameter with UNUSED would
+ * give a warning in a compilation where the parameter is indeed used,
+ * and not marking such a parameter would give a warning in a
+ * compilation where the parameter is unused.
+ */
#define MAYBE_UNUSED __attribute__((__unused__))

Quoted 6 lines

This is all good as pertains to function parameters. But the original reason we added MAYBE_UNUSED was actually for static functions that were auto-generated by the commit-slab macros. Saying "...marks a function parameter" implies to me that it's the only use. I don't know if we want to be more expansive here or not. Adding auto-generated macro functions should be quite a rarity, I'd think.

True. You can annotate types, variables, and functions with the attributes as well. How about saying something like this

MAYBE_UNUSED marks a function parameter that may be unused but whose use is not an error. It also can be applied to functions, types and variables.

and then keep the explanation of why you may want to use the maybe- variant as-is, using a function parameter as an example? Or I could rewrite "parameter" and "function parameter" in it with "thing" (with double quotes around), like:

Depending on a configuration, all uses of a "thing" may become #ifdef'ed away....

Unlike the use of deprecated attribute, our definition of MAYBE_UNUSED is not guarded with anything. Shouldn't we at least do

    #if defined(__GNUC__)
    #define MAYBE_UNUSED __attribute__((__unused__))
    #else
    #define MAYBE_UNUSED /* noop */
    #endif

or something, by the way?

Thanks.

#59 lore raw

PATCH v2 1/1 CodingGuidelines: also mention MAYBE_UNUSED

A function that uses a parameter in one build may lose all uses of the parameter in another build, depending on the configuration. A workaround for such a case, MAYBE_UNUSED, should also be mentioned when we recommend the use of UNUSED to our developers.

Keep the addition to the guideline short and document the criteria to choose between UNUSED and MAYBE_UNUSED near their definition.

---
 Documentation/CodingGuidelines |  5 +++--
 git-compat-util.h              | 24 ++++++++++++++++++++++++
 2 files changed, 27 insertions(+), 2 deletions(-)
Documentation/CodingGuidelines +3 -2
index d0fc7cfe60..3263245b03 100644
@@ -262,8 +262,9 @@ For C programs:
    like "error: unused parameter 'foo' [-Werror=unused-parameter]",
    which indicates that a function ignores its argument. If the unused
    parameter can't be removed (e.g., because the function is used as a
-   callback and has to match a certain interface), you can annotate the
-   individual parameters with the UNUSED keyword, like "int foo UNUSED".
+   callback and has to match a certain interface), you can annotate
+   the individual parameters with the UNUSED (or MAYBE_UNUSED)
+   keyword, like "int foo UNUSED".
 
  - We try to support a wide range of C compilers to compile Git with,
    including old ones.  As of Git v2.35.0 Git requires C99 (we check
git-compat-util.h +24 -0
index 71b4d23f03..e4a306dd56 100644
@@ -195,6 +195,19 @@ struct strbuf;
 #define _NETBSD_SOURCE 1
 #define _SGI_SOURCE 1
 
+/*
+ * UNUSED marks a function parameter that is always unused.  It also
+ * can be used to annotate a function, a variable, or a type that is
+ * always unused.
+ *
+ * A callback interface may dictate that a function accepts a
+ * parameter at that position, but the implementation of the function
+ * may not need to use the parameter.  In such a case, mark the parameter
+ * with UNUSED.
+ *
+ * When a parameter may be used or unused, depending on conditional
+ * compilation, consider using MAYBE_UNUSED instead.
+ */
 #if GIT_GNUC_PREREQ(4, 5)
 #define UNUSED __attribute__((unused)) \
 	__attribute__((deprecated ("parameter declared as UNUSED")))
@@ -649,6 +662,17 @@ static inline int git_has_dir_sep(const char *path)
 #define RESULT_MUST_BE_USED
 #endif
 
+/*
+ * MAYBE_UNUSED marks a function parameter that may be unused, but
+ * whose use is not an error.  It also can be used to annotate a
+ * function, a variable, or a type that may be unused.
+ *
+ * Depending on a configuration, all uses of such a thing may become
+ * #ifdef'ed away.  Marking it with UNUSED would give a warning in a
+ * compilation where it is indeed used, and not marking it at all
+ * would give a warning in a compilation where it is unused.  In such
+ * a case, MAYBE_UNUSED is the appropriate annotation to use.
+ */
 #define MAYBE_UNUSED __attribute__((__unused__))
 
 #include "compat/bswap.h"

Interdiff against v1:
diff --git a/git-compat-util.h b/git-compat-util.h index 23307ce780..e4a306dd56 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -196,7 +196,9 @@ struct strbuf;
#define _SGI_SOURCE 1

/*
- * UNUSED marks a function parameter that is always unused.
+ * UNUSED marks a function parameter that is always unused. It also
+ * can be used to annotate a function, a variable, or a type that is
+ * always unused.
*
* A callback interface may dictate that a function accepts a
* parameter at that position, but the implementation of the function
@@ -662,13 +664,14 @@ static inline int git_has_dir_sep(const char *path)

/*
* MAYBE_UNUSED marks a function parameter that may be unused, but
- * whose use is not an error.
+ * whose use is not an error. It also can be used to annotate a
+ * function, a variable, or a type that may be unused.

    *
  - * Depending on a configuration, all uses of a function parameter may
  - * become #ifdef'ed away.  Marking such a parameter with UNUSED would
  - * give a warning in a compilation where the parameter is indeed used,
  - * and not marking such a parameter would give a warning in a
  - * compilation where the parameter is unused.
  + * Depending on a configuration, all uses of such a thing may become
  + * #ifdef'ed away.  Marking it with UNUSED would give a warning in a
  + * compilation where it is indeed used, and not marking it at all
  + * would give a warning in a compilation where it is unused.  In such
  + * a case, MAYBE_UNUSED is the appropriate annotation to use.
    */

#define MAYBE_UNUSED __attribute__((__unused__))

Signature · 1 line
2.46.0-563-gaeb9b172ce
#60 lore raw

[PATCH 9/6] git-compat-util: guard definition of MAYBE_UNUSED with __GNUC__

Just like we only define UNUSED macro when __GNUC__ is defined, and fall back to an empty definition otherwise, we should do the same for MAYBE_UNUSED.

---
* Before I forget that we have discussed this, just as a documentation (read: this is not a patch to be applied).

I think this only matters when a compiler satisfies all three traits:

- does not define __GNUC__
- does have its own __attribute__() macro
- barfs on __attribute__((__unused__))

Otherwise we will define __attribute__(x) away to empty to cause no harm.

Since we have survived without complaints without such a guard for quite some time, it may be a sign that no compiler that knows __attribute__() that people ever tried to compile Git with barfs with __attribute__((__unused__)). I dunno.

 git-compat-util.h | 4 ++++
 1 file changed, 4 insertions(+)
git-compat-util.h +4 -0
index e4a306dd56..74ed581b5d 100644
@@ -673,7 +673,11 @@ static inline int git_has_dir_sep(const char *path)
  * would give a warning in a compilation where it is unused.  In such
  * a case, MAYBE_UNUSED is the appropriate annotation to use.
  */
+#ifdef __GNUC__
 #define MAYBE_UNUSED __attribute__((__unused__))
+#else
+#define MAYBE_UNUSED
+#endif
 
 #include "compat/bswap.h"
 
Signature · 1 line
2.46.0-563-gaeb9b172ce
#61 lore raw

Re: [PATCH 9/6] git-compat-util: guard definition of MAYBE_UNUSED with __GNUC__

Quoted 16 lines · Junio C Hamano

Just like we only define UNUSED macro when __GNUC__ is defined, and fall back to an empty definition otherwise, we should do the same for MAYBE_UNUSED.

---
* Before I forget that we have discussed this, just as a documentation (read: this is not a patch to be applied).

I think this only matters when a compiler satisfies all three traits:

- does not define __GNUC__
- does have its own __attribute__() macro
- barfs on __attribute__((__unused__))

Quoted 7 lines

Otherwise we will define __attribute__(x) away to empty to cause no harm.

Since we have survived without complaints without such a guard for quite some time, it may be a sign that no compiler that knows __attribute__() that people ever tried to compile Git with barfs with __attribute__((__unused__)). I dunno.

Yeah, I was surprised that this didn't have a guard and was not currently barfing on other compilers. And the answer is that we already turn __attribute__ into a noop on non-GNUC platforms.

Which made me wonder if UNUSED really needs its guards. It does, because it is defined early in the file, before the __attribute__ handling. I don't think we want to move it down, since it needs to be available for use by inline'd compat wrappers. But arguably we should move the attribute macro earlier in the file?

I don't know that it is really worth spending too much time futzing with, though.

git-compat-util.h static inline int git_has_dir_sep(const char *path) · line 680

Quoted 13 lines
git-compat-util.h +4 -0
index e4a306dd56..74ed581b5d 100644
@@ -673,7 +673,11 @@ static inline int git_has_dir_sep(const char *path)
  * would give a warning in a compilation where it is unused.  In such
  * a case, MAYBE_UNUSED is the appropriate annotation to use.
  */
+#ifdef __GNUC__
 #define MAYBE_UNUSED __attribute__((__unused__))
+#else
+#define MAYBE_UNUSED
+#endif

So yeah, I'm not necessarily opposed to this, but I don't think it's really doing anything in practice.

-Peff

#62 lore raw

Re: [PATCH 9/6] git-compat-util: guard definition of MAYBE_UNUSED with __GNUC__

Quoted 21 lines · Jeff King
Quoted 19 lines · Junio C Hamano

Just like we only define UNUSED macro when __GNUC__ is defined, and fall back to an empty definition otherwise, we should do the same for MAYBE_UNUSED.

---
* Before I forget that we have discussed this, just as a documentation (read: this is not a patch to be applied).

I think this only matters when a compiler satisfies all three traits:

- does not define __GNUC__
- does have its own __attribute__() macro
- barfs on __attribute__((__unused__))

Otherwise we will define __attribute__(x) away to empty to cause no harm.

Quoted 8 lines
Quoted 4 lines

Since we have survived without complaints without such a guard for quite some time, it may be a sign that no compiler that knows __attribute__() that people ever tried to compile Git with barfs with __attribute__((__unused__)). I dunno.

Yeah, I was surprised that this didn't have a guard and was not currently barfing on other compilers. And the answer is that we already turn __attribute__ into a noop on non-GNUC platforms.

Plus these non-GNUC platforms either

(1) do not have their own __attribute__, which lets us turn __attribute__() into noop, or

(2) have their own __attribute__, but they happen to support

     __attribute__((__unused__)).

If somebody has __attribute__() and does not support (__unused__) in it, use of MAYBE_UNUSED would be broken (maybe their __attribute__() supports other things but not unused).

Quoted 5 lines

Which made me wonder if UNUSED really needs its guards. It does, because it is defined early in the file, before the __attribute__ handling. I don't think we want to move it down, since it needs to be available for use by inline'd compat wrappers. But arguably we should move the attribute macro earlier in the file?

And moving __attribute__ definition earlier in the file would not help such a platform with broken __attribute__((__unused__))

Quoted 2 lines

I don't know that it is really worth spending too much time futzing with, though.

I am inclined to think it is not. So let's scrap the patch. The list archive will hopefully remember when it becomes necessary ;-)

#63 lore raw

Re: [PATCH v2] CodingGuidelines: also mention MAYBE_UNUSED

In reply to Junio C Hamano
Quoted 4 lines · Junio C Hamano

+/*
+ * MAYBE_UNUSED marks a function parameter that may be unused, but
+ * whose use is not an error. It also can be used to annotate a
+ * function, a variable, or a type that may be unused.

Quoted 8 lines

+ *
+ * Depending on a configuration, all uses of such a thing may become
+ * #ifdef'ed away. Marking it with UNUSED would give a warning in a
+ * compilation where it is indeed used, and not marking it at all
+ * would give a warning in a compilation where it is unused. In such
+ * a case, MAYBE_UNUSED is the appropriate annotation to use.
+ */
#define MAYBE_UNUSED __attribute__((__unused__))

Thanks, I think this is good. There's more nuanced discussion about when the "MAYBE" variant could be used for non-parameters, but I don't know that it's worth trying to enumerate every place we've found it useful.

-Peff

author
#64 lore raw

v3 cover add ref content check for files backend

In reply to shejialuo

Hi All:

This new version does the following things:

1. [PATCH v3 1/4]

+ the motivation of the previous commit message is too strong, this version improves this.

2. [PATCH v3 2/4]

+ Enhance the commit message to make things clearer.
+ Enhance the descriptions of the fsck message ids. Tell the user we may consider converting info to error later to let the user know this and report some feedback to us.
+ Use "goto" to avoid unnecessary indentation.
+ Use "test_commit" to create a single commit in the test file to avoid unnecessary setups.
+ Enhance the test cases by adding normal situation case test and add a new aggregation test to double verify the functionality.
+ Clean the "> $file" to ">$file" to make the code style correct.

3. [PATCH v3 3/4]

+ Enhance the commit message by better describing the motivation.
+ Change the fsck message name "badSymrefPointee" to "badSymrefTarget" to be align with the codebase. And talking more about what is the "bad" in the documentation.
+ Use idea from Junio to check the textual symref content.
+ Still keep the following code:

if (lstat(referent->buf, &st))
goto out;

if (S_ISDIR(st.st_mode)) {
ret = report(...);
goto out;
}

This is because that we cannot know whether "refs/heads/a" is a regular ref or a directory by using "check_refname_format". So we have to add this check. It may seem we have done this when iterating the "refs" directory. However, we do report error for other NON-symlink and NON-regular file type but for directory, we omit. We cannot say oh this is not right. So we need to explicitly check here.

+ Like [PATCH v3 2/4], enhance the test code.

4. [PATCH v3 4/4]

+ Enhance the commit message
+ Introduce a new fsck info "symlinkRef" to warn the user that we will see this warning as an error when we drop the symlink ref support.
+ Squash the following two patches into this patch:
https://lore.kernel.org/git/[email protected]/
https://lore.kernel.org/git/[email protected]/

Thanks,
Jialuo

shejialuo (4):
ref: initialize "fsck_ref_report" with zero
ref: add regular ref content check for files backend
ref: add symref content check for files backend
ref: add symlink ref content check for files backend

 Documentation/fsck-msgids.txt |  20 ++
 fsck.h                        |   5 +
 refs.c                        |   2 +-
 refs/files-backend.c          | 205 ++++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 334 ++++++++++++++++++++++++++++++++++
 6 files changed, 556 insertions(+), 12 deletions(-)

Range-diff against v2:
1: c49a216b70 ! 1: 9fdab751c1 ref: initialize "fsck_ref_report" with zero
@@ Commit message
NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

- The original code explicitly specifies the ".path" field to initialize
- the "fsck_ref_report" structure. However, it introduces confusion how we
- initialize the other fields. In order to avoid this, initialize the
- "fsck_ref_report" with zero to make clear that everything in
- "fsck_ref_report" is zero initialized.
+ The original code explicitly initializes the "path" member in the
+ "struct fsck_ref_report" to NULL (which implicitly 0-initializes other
+ members in the struct). It is more customary to use " {0} " to express
+ that we are 0-initializing everything. In order to be align with the the
+ codebase, initialize "fsck_ref_report" with zero.

@@ refs/files-backend.c: static int files_fsck_refs_name(struct ref_store *ref_stor

if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
- struct fsck_ref_report report = { .path = NULL };
-+ struct fsck_ref_report report = {0};
++ struct fsck_ref_report report = { 0 };

strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path); report.path = sb.buf;
2: 99e37b0304 ! 2: 4640b6e345 ref: add regular ref content check for files backend

    @@ Commit message
         We implicitly rely on "git-fsck(1)" to check the consistency of regular
         refs. However, when parsing the regular refs for files backend by using
         "files-backend.c::parse_loose_ref_contents", we allow the ref content to
    -    be end with no newline or contain some garbages.
    +    end with no newline or to contain some garbages.
     
    -    It may seem that we should report an error or warn fsck message to the
    -    user about above situations. However, there may be some third-party
    -    tools customizing the content of refs. We should not report an error
    -    fsck message.
    +    Even though we never create such loose refs ourselves, we have accepted
    +    such loose refs. So, it is entirely possible that some third-party tools
    +    may rely on such loose refs being valid. We should not report an error
    +    fsck message at current. But let's notice such a "curiously formatted"
    +    loose refs being valid and tell the user our findings, so we can access
    +    the possible extent of damage when we tighten the parsing rules in the
    +    future.
     
    -    And we cannot either report a warn fsck message to the user. This is
    -    because if the caller set the "strict" field in "fsck_options" to
    -    to upgrade the fsck warnings to errors.
    +    And it's not suitable to either report a warn fsck message to the user.
    +    This is because if the caller set the "strict" field in "fsck_options",
    +    fsck warns will be automatically upgraded to errors. We should not allow
    +    user to specify the "--strict" flag to upgrade the fsck warnings to
    +    errors at current. It might cause compatibility issue which may break
    +    the legacy repository. So we add the following two fsck infos to
    +    represent the situation where the ref content ends without newline or has
    +    garbages:
     
    -    We should not allow the user to upgrade the fsck warnings to errors. It
    -    might cause compatibility issue which will break the legacy repository.
    -    So we add the following two fsck infos to represent the situation where
    -    the ref content ends without newline or has garbages:
    +    1. "refMissingNewline(INFO)": A ref does not end with newline. This kind
    +       of ref may be considered ERROR in the future.
    +    2. "trailingRefContent(INFO)": A ref has trailing contents. This kind of
    +       ref may be considered ERROR in the future.
     
    -    1. "refMissingNewline(INFO)": A valid ref does not end with newline.
    -    2. "trailingRefContent(INFO)": A ref has trailing contents.
    -
    -    In "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN",
    -    and we can still warn the user about these situations when using
    -    "git-refs verify" without introducing compatibility issue.
    +    It may seem that we could not give the user any warnings by creating
    +    fsck infos. However, in "fsck.c::fsck_vreport", we will convert
    +    "FSCK_INFO" to "FSCK_WARN" and we can still warn the user about these
    +    situations when using "git-refs verify" without introducing
    +    compatibility issue.
     
         In current "git-fsck(1)", it will report an error when the ref content
         is bad, so we should following this to report an error to the user when
    @@ Documentation/fsck-msgids.txt
      	(WARN) Tree contains entries pointing to a null sha1.
      
     +`refMissingNewline`::
    -+	(INFO) A valid ref does not end with newline.
    ++	(INFO) A ref does not end with newline. This kind of ref may
    ++	be considered ERROR in the future.
     +
     +`trailingRefContent`::
    -+	(INFO) A ref has trailing contents.
    ++	(INFO) A ref has trailing contents. This kind of ref may be
    ++	considered ERROR in the future.
     +
      `treeNotSorted`::
      	(ERROR) A tree is not properly sorted.
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
     +	strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
     +	report.path = refname.buf;
     +
    -+	if (S_ISREG(iter->st.st_mode)) {
    -+		if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
    -+			ret = error_errno(_("%s/%s: unable to read the ref"),
    -+					  refs_check_dir, iter->relative_path);
    -+			goto cleanup;
    -+		}
    ++	if (S_ISLNK(iter->st.st_mode))
    ++		goto cleanup;
    ++
    ++	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
    ++		ret = error_errno(_("%s/%s: unable to read the ref"),
    ++				  refs_check_dir, iter->relative_path);
    ++		goto cleanup;
    ++	}
     +
    -+		if (parse_loose_ref_contents(ref_store->repo->hash_algo,
    -+					     ref_content.buf, &oid, &referent,
    -+					     &type, &trailing, &failure_errno)) {
    ++	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
    ++				     ref_content.buf, &oid, &referent,
    ++				     &type, &trailing, &failure_errno)) {
    ++		ret = fsck_report_ref(o, &report,
    ++				      FSCK_MSG_BAD_REF_CONTENT,
    ++				      "invalid ref content");
    ++		goto cleanup;
    ++	}
    ++
    ++	if (!(type & REF_ISSYMREF)) {
    ++		if (*trailing == '\0') {
     +			ret = fsck_report_ref(o, &report,
    -+					      FSCK_MSG_BAD_REF_CONTENT,
    -+					      "invalid ref content");
    ++					      FSCK_MSG_REF_MISSING_NEWLINE,
    ++					      "missing newline");
     +			goto cleanup;
     +		}
     +
    -+		if (!(type & REF_ISSYMREF)) {
    -+			if (*trailing == '\0') {
    -+				ret = fsck_report_ref(o, &report,
    -+						      FSCK_MSG_REF_MISSING_NEWLINE,
    -+						      "missing newline");
    -+				goto cleanup;
    -+			}
    -+
    -+			if (*trailing != '\n' || (*(trailing + 1) != '\0')) {
    -+				ret = fsck_report_ref(o, &report,
    -+						      FSCK_MSG_TRAILING_REF_CONTENT,
    -+						      "trailing garbage in ref");
    -+				goto cleanup;
    -+			}
    ++		if (*trailing != '\n' || (*(trailing + 1) != '\0')) {
    ++			ret = fsck_report_ref(o, &report,
    ++					      FSCK_MSG_TRAILING_REF_CONTENT,
    ++					      "trailing garbage in ref");
    ++			goto cleanup;
     +		}
    -+		goto cleanup;
     +	}
     +
     +cleanup:
    @@ t/t0602-reffiles-fsck.sh: test_expect_success 'ref name check should be adapted
      	test_must_be_empty err
      '
      
    -+test_expect_success 'regular ref content should be checked' '
    ++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 &&
    -+	git commit --allow-empty -m initial &&
    -+	git checkout -b branch-1 &&
    -+	git tag tag-1 &&
    -+	git commit --allow-empty -m second &&
    -+	git checkout -b branch-2 &&
    -+	git tag tag-2 &&
    -+	git checkout -b a/b/tag-2 &&
    ++	test_commit default &&
    ++	mkdir -p "$branch_dir_prefix/a/b" &&
     +
    -+	printf "%s" "$(git rev-parse branch-1)" > $branch_dir_prefix/branch-1-no-newline &&
    ++	git refs verify 2>err &&
    ++	test_must_be_empty err &&
    ++
    ++	printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/branch-1-no-newline: refMissingNewline: missing newline
    ++	warning: refs/heads/branch-no-newline: refMissingNewline: missing newline
     +	EOF
    -+	rm $branch_dir_prefix/branch-1-no-newline &&
    ++	rm $branch_dir_prefix/branch-no-newline &&
     +	test_cmp expect err &&
     +
    -+	printf "%s garbage" "$(git rev-parse branch-1)" > $branch_dir_prefix/branch-1-garbage &&
    ++	printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/branch-1-garbage: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/branch-garbage: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $branch_dir_prefix/branch-1-garbage &&
    ++	rm $branch_dir_prefix/branch-garbage &&
     +	test_cmp expect err &&
     +
    -+	printf "%s\n\n\n" "$(git rev-parse tag-1)" > $tag_dir_prefix/tag-1-garbage &&
    ++	printf "%s\n\n\n" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-1 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/tags/tag-1-garbage: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-1: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $tag_dir_prefix/tag-1-garbage &&
    ++	rm $tag_dir_prefix/tag-garbage-1 &&
     +	test_cmp expect err &&
     +
    -+	printf "%s\n\n\n  garbage" "$(git rev-parse tag-1)" > $tag_dir_prefix/tag-1-garbage &&
    ++	printf "%s\n\n\n  garbage" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-2 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/tags/tag-1-garbage: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-2: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $tag_dir_prefix/tag-1-garbage &&
    ++	rm $tag_dir_prefix/tag-garbage-2 &&
     +	test_cmp expect err &&
     +
    -+	printf "%s    garbage\n\na" "$(git rev-parse tag-2)" > $tag_dir_prefix/tag-2-garbage &&
    ++	printf "%s    garbage\n\na" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-3 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/tags/tag-2-garbage: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-3: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $tag_dir_prefix/tag-2-garbage &&
    ++	rm $tag_dir_prefix/tag-garbage-3 &&
     +	test_cmp expect err &&
     +
    -+	printf "%s garbage" "$(git rev-parse tag-1)" > $tag_dir_prefix/tag-1-garbage &&
    ++	printf "%s garbage" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-4 &&
     +	test_must_fail git -c fsck.trailingRefContent=error refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/tags/tag-1-garbage: trailingRefContent: trailing garbage in ref
    ++	error: refs/tags/tag-garbage-4: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $tag_dir_prefix/tag-1-garbage &&
    ++	rm $tag_dir_prefix/tag-garbage-4 &&
     +	test_cmp expect err &&
     +
    -+	printf "%sx" "$(git rev-parse tag-1)" > $tag_dir_prefix/tag-1-bad &&
    ++	printf "%sx" "$(git rev-parse main)" >$tag_dir_prefix/tag-bad-1 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/tags/tag-1-bad: badRefContent: invalid ref content
    ++	error: refs/tags/tag-bad-1: badRefContent: invalid ref content
     +	EOF
    -+	rm $tag_dir_prefix/tag-1-bad &&
    ++	rm $tag_dir_prefix/tag-bad-1 &&
     +	test_cmp expect err &&
     +
    -+	printf "xfsazqfxcadas" > $tag_dir_prefix/tag-2-bad &&
    ++	printf "xfsazqfxcadas" >$tag_dir_prefix/tag-bad-2 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/tags/tag-2-bad: badRefContent: invalid ref content
    ++	error: refs/tags/tag-bad-2: badRefContent: invalid ref content
     +	EOF
    -+	rm $tag_dir_prefix/tag-2-bad &&
    ++	rm $tag_dir_prefix/tag-bad-2 &&
     +	test_cmp expect err &&
     +
    -+	printf "xfsazqfxcadas" > $branch_dir_prefix/a/b/branch-2-bad &&
    ++	printf "xfsazqfxcadas" >$branch_dir_prefix/a/b/branch-bad &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/a/b/branch-2-bad: badRefContent: invalid ref content
    ++	error: refs/heads/a/b/branch-bad: badRefContent: invalid ref content
     +	EOF
    -+	rm $branch_dir_prefix/a/b/branch-2-bad &&
    ++	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" &&
    ++
    ++	printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline &&
    ++	printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage &&
    ++	printf "%s\n\n\n" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-1 &&
    ++	printf "%s\n\n\n  garbage" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-2 &&
    ++	printf "%s    garbage\n\na" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-3 &&
    ++	printf "%s garbage" "$(git rev-parse main)" >$tag_dir_prefix/tag-garbage-4 &&
    ++	printf "%sx" "$(git rev-parse main)" >$tag_dir_prefix/tag-bad-1 &&
    ++	printf "xfsazqfxcadas" >$tag_dir_prefix/tag-bad-2 &&
    ++	printf "xfsazqfxcadas" >$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: invalid ref content
    ++	error: refs/tags/tag-bad-1: badRefContent: invalid ref content
    ++	error: refs/tags/tag-bad-2: badRefContent: invalid ref content
    ++	warning: refs/heads/branch-garbage: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/branch-no-newline: refMissingNewline: missing newline
    ++	warning: refs/tags/tag-garbage-1: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-2: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-3: trailingRefContent: trailing garbage in ref
    ++	warning: refs/tags/tag-garbage-4: trailingRefContent: trailing garbage in ref
    ++	EOF
    ++	sort err >sorted_err &&
    ++	test_cmp expect sorted_err
    ++'
     +
      test_done
3:  76dcf6bf58 ! 3:  0691e2960d ref: add symbolic ref content check for files backend
    @@ Metadata
     Author: shejialuo <[email protected]>
     
      ## Commit message ##
    -    ref: add symbolic ref content check for files backend
    +    ref: add symref content check for files backend
     
         We have already introduced the checks for regular refs. There is no need
    -    to check the consistency of the target which the symbolic ref points to.
    -    Instead, we just check the content of the symbolic ref itself.
    +    to check the consistency of the target which the symref points to.
    +    Instead, we just need to check the content of teh symref itself.
     
    -    In order to check the content of the symbolic ref, create a function
    -    "files_fsck_symref_target". It will first check whether the "pointee" is
    -    under the "refs/" directory and then we will check the "pointee" itself.
    +    In order to check the content of the symref, create a function
    +    "files_fsck_symref_target". It will first check whether the "referent"
    +    is under the "refs/" directory and then we will check the symref
    +    contents.
     
    -    There is no specification about the content of the symbolic ref.
    -    Although we do write "ref: %s\n" to create a symbolic ref by using
    -    "git-symbolic-ref(1)" command. However, this is not mandatory. We still
    -    accept symbolic refs with null trailing garbage. Put it more specific,
    -    the following are correct:
    +    A regular file is accepted as a textual symref if it begins with
    +    "ref:", followed by zero or more whitespaces, followed by the full
    +    refname, followed only by whitespace characters. We always write
    +    a single SP after "ref:" and a single LF after the refname, but
    +    third-party reimplementations of Git may have taken advantage of the
    +    looser syntax. Put it more specific, we accept the following contents
    +    of the symref:
     
         1. "ref: refs/heads/master   "
         2. "ref: refs/heads/master   \n  \n"
         3. "ref: refs/heads/master\n\n"
     
    -    But we do not allow any non-null trailing garbage. The following are bad
    -    symbolic contents which will be reported as fsck error by "git-fsck(1)".
    +    But we do not allow any other trailing garbage. The followings are bad
    +    symref contents which will be reported as fsck error by "git-fsck(1)".
     
         1. "ref: refs/heads/master garbage\n"
         2. "ref: refs/heads/master \n\n\n garbage  "
     
    -    In order to provide above checks, we will use "strrchr" to check whether
    -    we have newline in the ref content. Then we will check the name of the
    -    "pointee" is correct by using "check_refname_format". If the function
    -    fails, we need to trim the "pointee" to see whether the null-garbage
    -    causes the function fails. If so, we need to report that there is
    -    null-garbage in the symref content. Otherwise, we should report the user
    -    the "pointee" is bad.
    +    In order to provide above checks, we will first check whether the symref
    +    content misses the newline by peeking the last byte of the "referent" to
    +    see whether it is '\n'.
    +
    +    And we will remember the untrimmed length of the "referent" and call
    +    "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format"
    +    to chceck whether the trimmed referent format is valid. If not, we will
    +    report to the user that the symref points to referent which has invalid
    +    format. If it is valid, we will compare the untrimmed length and trimmed
    +    length, if they are not the same, we need to warn the user there is some
    +    trailing garbage in the symref content.
    +
    +    At last, we need to check whether the referent is the directory. We
    +    cannot distinguish whether the "refs/heads/a" is a directory or not by
    +    using "check_refname_format". We have already checked bad file type when
    +    iterating the "refs/" directory but we ignore the directory. Thus, we
    +    need to explicitly add check here.
     
         Mentored-by: Patrick Steinhardt <[email protected]>
         Mentored-by: Karthik Nayak <[email protected]>
    @@ Documentation/fsck-msgids.txt
      `badRefName`::
      	(ERROR) A ref has an invalid format.
      
    -+`badSymrefPointee`::
    -+	(ERROR) The pointee of a symref is bad.
    ++`badSymrefTarget`::
    ++	(ERROR) The symref target points outside the ref directory or
    ++	the name of the symref target is invalid.
     +
      `badTagName`::
      	(INFO) A tag has an invalid format.
    @@ fsck.h: enum fsck_msg_type {
      	FUNC(BAD_REF_CONTENT, ERROR) \
      	FUNC(BAD_REF_FILETYPE, ERROR) \
      	FUNC(BAD_REF_NAME, ERROR) \
    -+	FUNC(BAD_SYMREF_POINTEE, ERROR) \
    ++	FUNC(BAD_SYMREF_TARGET, ERROR) \
      	FUNC(BAD_TIMEZONE, ERROR) \
      	FUNC(BAD_TREE, ERROR) \
      	FUNC(BAD_TREE_SHA1, ERROR) \
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
      				  struct dir_iterator *iter);
      
     +/*
    -+ * Check the symref "pointee_name" and "pointee_path". The caller should
    -+ * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
    -+ * would be the content after "refs:".
    ++ * Check the symref "referent" and "referent_path". For textual symref,
    ++ * "referent" would be the content after "refs:".
     + */
     +static int files_fsck_symref_target(struct fsck_options *o,
     +				    struct fsck_ref_report *report,
    -+				    const char *refname,
    -+				    struct strbuf *pointee_name,
    -+				    struct strbuf *pointee_path)
    ++				    struct strbuf *referent,
    ++				    struct strbuf *referent_path)
     +{
    -+	const char *newline_pos = NULL;
    ++	size_t len = referent->len - 1;
     +	const char *p = NULL;
     +	struct stat st;
     +	int ret = 0;
     +
    -+	if (!skip_prefix(pointee_name->buf, "refs/", &p)) {
    ++	if (!skip_prefix(referent->buf, "refs/", &p)) {
     +
     +		ret = fsck_report_ref(o, report,
    -+				      FSCK_MSG_BAD_SYMREF_POINTEE,
    ++				      FSCK_MSG_BAD_SYMREF_TARGET,
     +				      "points to ref outside the refs directory");
     +		goto out;
     +	}
     +
    -+	newline_pos = strrchr(p, '\n');
    -+	if (!newline_pos || *(newline_pos + 1)) {
    ++	if (referent->buf[referent->len - 1] != '\n') {
     +		ret = fsck_report_ref(o, report,
     +				      FSCK_MSG_REF_MISSING_NEWLINE,
     +				      "missing newline");
    ++		len++;
     +	}
     +
    -+	if (check_refname_format(pointee_name->buf, 0)) {
    -+		/*
    -+		 * When containing null-garbage, "check_refname_format" will
    -+		 * fail, we should trim the "pointee" to check again.
    -+		 */
    -+		strbuf_rtrim(pointee_name);
    -+		if (!check_refname_format(pointee_name->buf, 0)) {
    -+			ret = fsck_report_ref(o, report,
    -+					      FSCK_MSG_TRAILING_REF_CONTENT,
    -+					      "trailing null-garbage");
    -+			goto out;
    -+		}
    -+
    ++	strbuf_rtrim(referent);
    ++	if (check_refname_format(referent->buf, 0)) {
     +		ret = fsck_report_ref(o, report,
    -+				      FSCK_MSG_BAD_SYMREF_POINTEE,
    ++				      FSCK_MSG_BAD_SYMREF_TARGET,
     +				      "points to refname with invalid format");
    ++		goto out;
    ++	}
    ++
    ++	if (len != referent->len) {
    ++		ret = fsck_report_ref(o, report,
    ++				      FSCK_MSG_TRAILING_REF_CONTENT,
    ++				      "trailing garbage in ref");
     +	}
     +
     +	/*
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
     +	 * ref that does not exist yet. If the target ref does not exist, just
     +	 * skip the check for the file type.
     +	 */
    -+	if (lstat(pointee_path->buf, &st) < 0)
    ++	if (lstat(referent_path->buf, &st))
     +		goto out;
     +
    -+	if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
    ++	/*
    ++	 * We cannot distinguish whether "refs/heads/a" is directory or nots by
    ++	 * using "check_refname_format(referent->buf, 0)". Instead, we need to
    ++	 * check the file type of the target.
    ++	 */
    ++	if (S_ISDIR(st.st_mode)) {
     +		ret = fsck_report_ref(o, report,
    -+				      FSCK_MSG_BAD_SYMREF_POINTEE,
    -+				      "points to an invalid file type");
    ++				      FSCK_MSG_BAD_SYMREF_TARGET,
    ++				      "points to the directory");
     +		goto out;
     +	}
     +
    @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_st
      				   const char *refs_check_dir,
      				   struct dir_iterator *iter)
      {
    -+	struct strbuf pointee_path = STRBUF_INIT;
    ++	struct strbuf referent_path = STRBUF_INIT;
      	struct strbuf ref_content = STRBUF_INIT;
      	struct strbuf referent = STRBUF_INIT;
      	struct strbuf refname = STRBUF_INIT;
     @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    - 						      "trailing garbage in ref");
    - 				goto cleanup;
    - 			}
    -+		} else {
    -+			strbuf_addf(&pointee_path, "%s/%s",
    -+				    ref_store->gitdir, referent.buf);
    -+			ret = files_fsck_symref_target(o, &report, refname.buf,
    -+						       &referent,
    -+						       &pointee_path);
    + 					      "trailing garbage in ref");
    + 			goto cleanup;
      		}
    - 		goto cleanup;
    ++	} else {
    ++		strbuf_addf(&referent_path, "%s/%s",
    ++			    ref_store->gitdir, referent.buf);
    ++		/*
    ++		 * the referent may contain the spaces and the newline, need to
    ++		 * trim for path.
    ++		 */
    ++		strbuf_rtrim(&referent_path);
    ++		ret = files_fsck_symref_target(o, &report,
    ++					       &referent,
    ++					       &referent_path);
      	}
    -@@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    + 
    + cleanup:
      	strbuf_release(&refname);
      	strbuf_release(&ref_content);
      	strbuf_release(&referent);
    -+	strbuf_release(&pointee_path);
    ++	strbuf_release(&referent_path);
      	return ret;
      }
      
     
      ## t/t0602-reffiles-fsck.sh ##
    -@@ t/t0602-reffiles-fsck.sh: test_expect_success 'regular ref content should be checked' '
    - 	test_cmp expect err
    +@@ t/t0602-reffiles-fsck.sh: test_expect_success 'regular ref content should be checked (aggregate)' '
    + 	test_cmp expect sorted_err
      '
      
    -+test_expect_success 'symbolic ref content should be checked' '
    ++test_expect_success 'textual symref 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 &&
    -+	git commit --allow-empty -m initial &&
    -+	git checkout -b branch-1 &&
    -+	git tag tag-1 &&
    -+	git checkout -b a/b/branch-2 &&
    ++	test_commit default &&
    ++	mkdir -p "$branch_dir_prefix/a/b" &&
    ++
    ++	printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
    ++	git refs verify 2>err &&
    ++	rm $branch_dir_prefix/branch-good &&
    ++	test_must_be_empty err &&
    ++
    ++	printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
    ++	git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	warning: refs/heads/branch-no-newline-1: refMissingNewline: missing newline
    ++	EOF
    ++	rm $branch_dir_prefix/branch-no-newline-1 &&
    ++	test_cmp expect err &&
     +
    -+	printf "ref: refs/heads/branch" > $branch_dir_prefix/branch-1-no-newline &&
    ++	printf "ref: refs/heads/branch     " >$branch_dir_prefix/a/b/branch-trailing-1 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/branch-1-no-newline: refMissingNewline: missing newline
    ++	warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: missing newline
    ++	warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $branch_dir_prefix/branch-1-no-newline &&
    ++	rm $branch_dir_prefix/a/b/branch-trailing-1 &&
     +	test_cmp expect err &&
     +
    -+	printf "ref: refs/heads/branch     " > $branch_dir_prefix/a/b/branch-trailing &&
    ++	printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/a/b/branch-trailing: refMissingNewline: missing newline
    -+	warning: refs/heads/a/b/branch-trailing: trailingRefContent: trailing null-garbage
    ++	warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $branch_dir_prefix/a/b/branch-trailing &&
    ++	rm $branch_dir_prefix/a/b/branch-trailing-2 &&
     +	test_cmp expect err &&
     +
    -+	printf "ref: refs/heads/branch\n\n" > $branch_dir_prefix/a/b/branch-trailing &&
    ++	printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/a/b/branch-trailing: trailingRefContent: trailing null-garbage
    ++	warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $branch_dir_prefix/a/b/branch-trailing &&
    ++	rm $branch_dir_prefix/a/b/branch-trailing-3 &&
     +	test_cmp expect err &&
     +
    -+	printf "ref: refs/heads/branch \n\n " > $branch_dir_prefix/a/b/branch-trailing &&
    ++	printf "ref: refs/heads/branch \n  " >$branch_dir_prefix/a/b/branch-complicated &&
     +	git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	warning: refs/heads/a/b/branch-trailing: refMissingNewline: missing newline
    -+	warning: refs/heads/a/b/branch-trailing: trailingRefContent: trailing null-garbage
    ++	warning: refs/heads/a/b/branch-complicated: refMissingNewline: missing newline
    ++	warning: refs/heads/a/b/branch-complicated: trailingRefContent: trailing garbage in ref
     +	EOF
    -+	rm $branch_dir_prefix/a/b/branch-trailing &&
    ++	rm $branch_dir_prefix/a/b/branch-complicated &&
     +	test_cmp expect err &&
     +
    -+	printf "ref: refs/heads/.branch\n" > $branch_dir_prefix/branch-2-bad &&
    ++	printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/branch-2-bad: badSymrefPointee: points to refname with invalid format
    ++	error: refs/heads/branch-bad-1: badSymrefTarget: points to refname with invalid format
     +	EOF
    -+	rm $branch_dir_prefix/branch-2-bad &&
    ++	rm $branch_dir_prefix/branch-bad-1 &&
    ++	test_cmp expect err &&
    ++
    ++	printf "ref: reflogs/heads/main\n" >$branch_dir_prefix/branch-bad-2 &&
    ++	test_must_fail git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	error: refs/heads/branch-bad-2: badSymrefTarget: points to ref outside the refs directory
    ++	EOF
    ++	rm $branch_dir_prefix/branch-bad-2 &&
    ++	test_cmp expect err &&
    ++
    ++	printf "ref: refs/heads/a\n" >$branch_dir_prefix/branch-bad-3 &&
    ++	test_must_fail git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	error: refs/heads/branch-bad-3: badSymrefTarget: points to the directory
    ++	EOF
    ++	rm $branch_dir_prefix/branch-bad-3 &&
     +	test_cmp expect err
     +'
    ++
    ++test_expect_success 'textual symref 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" &&
    ++
    ++	printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
    ++	printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
    ++	printf "ref: refs/heads/branch     " >$branch_dir_prefix/a/b/branch-trailing-1 &&
    ++	printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
    ++	printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
    ++	printf "ref: refs/heads/branch \n  " >$branch_dir_prefix/a/b/branch-complicated &&
    ++	printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
    ++	printf "ref: reflogs/heads/main\n" >$branch_dir_prefix/branch-bad-2 &&
    ++	printf "ref: refs/heads/a\n" >$branch_dir_prefix/branch-bad-3 &&
    ++
    ++	test_must_fail git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	error: refs/heads/branch-bad-1: badSymrefTarget: points to refname with invalid format
    ++	error: refs/heads/branch-bad-2: badSymrefTarget: points to ref outside the refs directory
    ++	error: refs/heads/branch-bad-3: badSymrefTarget: points to the directory
    ++	warning: refs/heads/a/b/branch-complicated: refMissingNewline: missing newline
    ++	warning: refs/heads/a/b/branch-complicated: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: missing newline
    ++	warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: trailing garbage in ref
    ++	warning: refs/heads/branch-no-newline-1: refMissingNewline: missing newline
    ++	EOF
    ++	sort err >sorted_err &&
    ++	test_cmp expect sorted_err
    ++'
     +
      test_done
4:  2008f8635c ! 4:  4105bfa1e3 ref: add symlink ref check for files backend
    @@ Metadata
     Author: shejialuo <[email protected]>
     
      ## Commit message ##
    -    ref: add symlink ref check for files backend
    +    ref: add symlink ref content check for files backend
     
         We have already introduced "files_fsck_symref_target". We should reuse
    -    this function to handle the symrefs which are legacy symbolic links. We
    -    should not check the trailing garbage for symbolic links. Add a new
    +    this function to handle the symrefs which use legacy symbolic links. We
    +    should not check the trailing garbage for symbolic refs. Add a new
         parameter "symbolic_link" to disable some checks which should only be
    -    used for symbolic ref.
    +    executed for textual symrefs.
     
    -    We firstly use the "strbuf_add_real_path" to resolve the symlinks and
    -    get the absolute path "pointee_path" which the symlink ref points to.
    -    Then we can get the absolute path "abs_gitdir" of the "gitdir". By
    -    combining "pointee_path" and "abs_gitdir", we can extract the
    +    We firstly use the "strbuf_add_real_path" to resolve the symlink and
    +    get the absolute path "referent_path" which the symlink ref points
    +    to. Then we can get the absolute path "abs_gitdir" of the "gitdir".
    +    By combining "referent_path" and "abs_gitdir", we can extract the
         "referent". Thus, we can reuse "files_fsck_symref_target" function to
         seamlessly check the symlink refs.
     
    +    Because we are going to drop support for "core.prefersymlinkrefs", add a
    +    new fsck message "symlinkRef" to let the user be aware of this
    +    information.
    +
         Mentored-by: Patrick Steinhardt <[email protected]>
         Mentored-by: Karthik Nayak <[email protected]>
         Signed-off-by: shejialuo <[email protected]>
     
    + ## Documentation/fsck-msgids.txt ##
    +@@
    + 	(INFO) A ref does not end with newline. This kind of ref may
    + 	be considered ERROR in the future.
    + 
    ++`symlinkRef`::
    ++	(INFO) A symref uses the symbolic link. This kind of symref may
    ++	be considered ERROR in the future when totally dropping the
    ++	symlink support.
    ++
    + `trailingRefContent`::
    + 	(INFO) A ref has trailing contents. This kind of ref may be
    + 	considered ERROR in the future.
    +
    + ## fsck.h ##
    +@@ fsck.h: enum fsck_msg_type {
    + 	FUNC(BAD_TAG_NAME, INFO) \
    + 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
    + 	FUNC(REF_MISSING_NEWLINE, INFO) \
    ++	FUNC(SYMLINK_REF, INFO) \
    + 	FUNC(TRAILING_REF_CONTENT, INFO) \
    + 	/* ignored (elevated when requested) */ \
    + 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
    +
      ## refs/files-backend.c ##
     @@
      #include "../git-compat-util.h"
    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
      			goto out;
      		}
     @@ refs/files-backend.c: typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
    + 
      /*
    -  * Check the symref "pointee_name" and "pointee_path". The caller should
    -  * make sure that "pointee_path" is absolute. For symbolic ref, "pointee_name"
    -- * would be the content after "refs:".
    -+ * would be the content after "refs:". For symblic link, "pointee_name" would
    -+ * be the relative path agaignst "gitdir".
    +  * Check the symref "referent" and "referent_path". For textual symref,
    +- * "referent" would be the content after "refs:".
    ++ * "referent" would be the content after "refs:". For symlink ref,
    ++ * "referent" would be the relative path agaignst "gitdir" which should
    ++ * be the same as the textual symref literally.
       */
      static int files_fsck_symref_target(struct fsck_options *o,
      				    struct fsck_ref_report *report,
    --				    const char *refname,
    - 				    struct strbuf *pointee_name,
    --				    struct strbuf *pointee_path)
    -+				    struct strbuf *pointee_path,
    + 				    struct strbuf *referent,
    +-				    struct strbuf *referent_path)
    ++				    struct strbuf *referent_path,
     +				    unsigned int symbolic_link)
      {
    - 	const char *newline_pos = NULL;
    + 	size_t len = referent->len - 1;
      	const char *p = NULL;
     @@ refs/files-backend.c: static int files_fsck_symref_target(struct fsck_options *o,
      		goto out;
      	}
      
    --	newline_pos = strrchr(p, '\n');
    --	if (!newline_pos || *(newline_pos + 1)) {
    --		ret = fsck_report_ref(o, report,
    --				      FSCK_MSG_REF_MISSING_NEWLINE,
    --				      "missing newline");
    -+	if (!symbolic_link) {
    -+		newline_pos = strrchr(p, '\n');
    -+		if (!newline_pos || *(newline_pos + 1)) {
    -+			ret = fsck_report_ref(o, report,
    -+					      FSCK_MSG_REF_MISSING_NEWLINE,
    -+					      "missing newline");
    -+		}
    +-	if (referent->buf[referent->len - 1] != '\n') {
    ++	if (!symbolic_link && referent->buf[referent->len - 1] != '\n') {
    + 		ret = fsck_report_ref(o, report,
    + 				      FSCK_MSG_REF_MISSING_NEWLINE,
    + 				      "missing newline");
    + 		len++;
      	}
      
    - 	if (check_refname_format(pointee_name->buf, 0)) {
    --		/*
    --		 * When containing null-garbage, "check_refname_format" will
    --		 * fail, we should trim the "pointee" to check again.
    --		 */
    --		strbuf_rtrim(pointee_name);
    --		if (!check_refname_format(pointee_name->buf, 0)) {
    --			ret = fsck_report_ref(o, report,
    --					      FSCK_MSG_TRAILING_REF_CONTENT,
    --					      "trailing null-garbage");
    --			goto out;
    -+		if (!symbolic_link) {
    -+			/*
    -+			* When containing null-garbage, "check_refname_format" will
    -+			* fail, we should trim the "pointee" to check again.
    -+			*/
    -+			strbuf_rtrim(pointee_name);
    -+			if (!check_refname_format(pointee_name->buf, 0)) {
    -+				ret = fsck_report_ref(o, report,
    -+						      FSCK_MSG_TRAILING_REF_CONTENT,
    -+						      "trailing null-garbage");
    -+				goto out;
    -+			}
    - 		}
    +-	strbuf_rtrim(referent);
    ++	if (!symbolic_link)
    ++		strbuf_rtrim(referent);
    ++
    + 	if (check_refname_format(referent->buf, 0)) {
    + 		ret = fsck_report_ref(o, report,
    + 				      FSCK_MSG_BAD_SYMREF_TARGET,
    +@@ refs/files-backend.c: static int files_fsck_symref_target(struct fsck_options *o,
    + 		goto out;
    + 	}
      
    +-	if (len != referent->len) {
    ++	if (!symbolic_link && len != referent->len) {
      		ret = fsck_report_ref(o, report,
    + 				      FSCK_MSG_TRAILING_REF_CONTENT,
    + 				      "trailing garbage in ref");
     @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
      {
    - 	struct strbuf pointee_path = STRBUF_INIT;
    + 	struct strbuf referent_path = STRBUF_INIT;
      	struct strbuf ref_content = STRBUF_INIT;
     +	struct strbuf abs_gitdir = STRBUF_INIT;
      	struct strbuf referent = STRBUF_INIT;
      	struct strbuf refname = STRBUF_INIT;
      	struct fsck_ref_report report = {0};
    -+	const char *pointee_name = NULL;
     +	unsigned int symbolic_link = 0;
      	const char *trailing = NULL;
      	unsigned int type = 0;
      	int failure_errno = 0;
     @@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    - 		} else {
    - 			strbuf_addf(&pointee_path, "%s/%s",
    - 				    ref_store->gitdir, referent.buf);
    --			ret = files_fsck_symref_target(o, &report, refname.buf,
    -+			ret = files_fsck_symref_target(o, &report,
    - 						       &referent,
    --						       &pointee_path);
    -+						       &pointee_path,
    -+						       symbolic_link);
    - 		}
    - 		goto cleanup;
    - 	}
    + 	strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
    + 	report.path = refname.buf;
      
    -+	symbolic_link = 1;
    -+
    -+	strbuf_add_real_path(&pointee_path, iter->path.buf);
    -+	strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
    -+	strbuf_normalize_path(&abs_gitdir);
    -+	if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
    -+		strbuf_addch(&abs_gitdir, '/');
    +-	if (S_ISLNK(iter->st.st_mode))
    ++	if (S_ISLNK(iter->st.st_mode)) {
    ++		const char* relative_referent_path;
     +
    -+	if (!skip_prefix(pointee_path.buf, abs_gitdir.buf, &pointee_name)) {
    ++		symbolic_link = 1;
     +		ret = fsck_report_ref(o, &report,
    -+				      FSCK_MSG_BAD_SYMREF_POINTEE,
    -+				      "point to target outside gitdir");
    -+		goto cleanup;
    -+	}
    ++				      FSCK_MSG_SYMLINK_REF,
    ++				      "use deprecated symbolic link for symref");
     +
    -+	strbuf_addstr(&referent, pointee_name);
    -+	ret = files_fsck_symref_target(o, &report,
    -+				       &referent, &pointee_path,
    -+				       symbolic_link);
    ++		strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
    ++		strbuf_normalize_path(&abs_gitdir);
    ++		if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
    ++			strbuf_addch(&abs_gitdir, '/');
     +
    ++		strbuf_add_real_path(&referent_path, iter->path.buf);
    ++
    ++		if (!skip_prefix(referent_path.buf,
    ++				 abs_gitdir.buf,
    ++				 &relative_referent_path)) {
    ++			ret = fsck_report_ref(o, &report,
    ++					      FSCK_MSG_BAD_SYMREF_TARGET,
    ++					      "point to target outside gitdir");
    ++			goto cleanup;
    ++		}
    ++
    ++		strbuf_addstr(&referent, relative_referent_path);
    ++		ret = files_fsck_symref_target(o, &report,
    ++					       &referent, &referent_path,
    ++					       symbolic_link);
    ++
    + 		goto cleanup;
    ++	}
    + 
    + 	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
    + 		ret = error_errno(_("%s/%s: unable to read the ref"),
    +@@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
    + 		strbuf_rtrim(&referent_path);
    + 		ret = files_fsck_symref_target(o, &report,
    + 					       &referent,
    +-					       &referent_path);
    ++					       &referent_path,
    ++					       symbolic_link);
    + 	}
    + 
      cleanup:
    - 	strbuf_release(&refname);
    +@@ refs/files-backend.c: static int files_fsck_refs_content(struct ref_store *ref_store,
      	strbuf_release(&ref_content);
      	strbuf_release(&referent);
    - 	strbuf_release(&pointee_path);
    + 	strbuf_release(&referent_path);
     +	strbuf_release(&abs_gitdir);
      	return ret;
      }
      
     
      ## t/t0602-reffiles-fsck.sh ##
    -@@ t/t0602-reffiles-fsck.sh: test_expect_success 'symbolic ref content should be checked' '
    - 	test_cmp expect err
    +@@ t/t0602-reffiles-fsck.sh: test_expect_success 'textual symref content should be checked (aggregate)' '
    + 	test_cmp expect sorted_err
      '
      
    -+test_expect_success SYMLINKS 'symbolic ref (symbolic link) content should be checked' '
    ++test_expect_success SYMLINKS 'symlink symref 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 &&
    -+	git commit --allow-empty -m initial &&
    -+	git checkout -b branch-1 &&
    -+	git tag tag-1 &&
    -+	git checkout -b a/b/branch-2 &&
    ++	test_commit default &&
    ++	mkdir -p "$branch_dir_prefix/a/b" &&
    ++
    ++	ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
    ++	git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
    ++	EOF
    ++	rm $branch_dir_prefix/branch-symbolic-good &&
    ++	test_cmp expect err &&
    ++
    ++	ln -sf ../../../../branch $branch_dir_prefix/branch-symbolic-1 &&
    ++	test_must_fail git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	warning: refs/heads/branch-symbolic-1: symlinkRef: use deprecated symbolic link for symref
    ++	error: refs/heads/branch-symbolic-1: badSymrefTarget: point to target outside gitdir
    ++	EOF
    ++	rm $branch_dir_prefix/branch-symbolic-1 &&
    ++	test_cmp expect err &&
     +
    -+	ln -sf ../../../../branch $branch_dir_prefix/branch-symbolic &&
    ++	ln -sf ../../logs/branch-bad $branch_dir_prefix/branch-symbolic-2 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/branch-symbolic: badSymrefPointee: point to target outside gitdir
    ++	warning: refs/heads/branch-symbolic-2: symlinkRef: use deprecated symbolic link for symref
    ++	error: refs/heads/branch-symbolic-2: badSymrefTarget: points to ref outside the refs directory
     +	EOF
    -+	rm $branch_dir_prefix/branch-symbolic &&
    ++	rm $branch_dir_prefix/branch-symbolic-2 &&
     +	test_cmp expect err &&
     +
    -+	ln -sf ../../logs/branch-bad $branch_dir_prefix/branch-symbolic &&
    ++	ln -sf ./"branch   space" $branch_dir_prefix/branch-symbolic-3 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/branch-symbolic: badSymrefPointee: points to ref outside the refs directory
    ++	warning: refs/heads/branch-symbolic-3: symlinkRef: use deprecated symbolic link for symref
    ++	error: refs/heads/branch-symbolic-3: badSymrefTarget: points to refname with invalid format
     +	EOF
    -+	rm $branch_dir_prefix/branch-symbolic &&
    ++	rm $branch_dir_prefix/branch-symbolic-3 &&
     +	test_cmp expect err &&
     +
    -+	ln -sf ./"branch   space" $branch_dir_prefix/branch-symbolic &&
    ++	ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/branch-symbolic: badSymrefPointee: points to refname with invalid format
    ++	warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
    ++	error: refs/tags/tag-symbolic-1: badSymrefTarget: points to refname with invalid format
     +	EOF
    -+	rm $branch_dir_prefix/branch-symbolic &&
    ++	rm $tag_dir_prefix/tag-symbolic-1 &&
     +	test_cmp expect err &&
     +
    -+	ln -sf ./".branch" $branch_dir_prefix/branch-symbolic &&
    ++	ln -sf ./ $tag_dir_prefix/tag-symbolic-2 &&
     +	test_must_fail git refs verify 2>err &&
     +	cat >expect <<-EOF &&
    -+	error: refs/heads/branch-symbolic: badSymrefPointee: points to refname with invalid format
    ++	warning: refs/tags/tag-symbolic-2: symlinkRef: use deprecated symbolic link for symref
    ++	error: refs/tags/tag-symbolic-2: badSymrefTarget: points to the directory
     +	EOF
    -+	rm $branch_dir_prefix/branch-symbolic &&
    ++	rm $tag_dir_prefix/tag-symbolic-2 &&
     +	test_cmp expect err
     +'
    ++
    ++test_expect_success SYMLINKS 'symlink symref 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" &&
    ++
    ++	ln -sf ./main $branch_dir_prefix/branch-symbolic-good &&
    ++	ln -sf ../../../../branch $branch_dir_prefix/branch-symbolic-1 &&
    ++	ln -sf ../../logs/branch-bad $branch_dir_prefix/branch-symbolic-2 &&
    ++	ln -sf ./"branch   space" $branch_dir_prefix/branch-symbolic-3 &&
    ++	ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 &&
    ++	ln -sf ./ $tag_dir_prefix/tag-symbolic-2 &&
    ++
    ++	test_must_fail git refs verify 2>err &&
    ++	cat >expect <<-EOF &&
    ++	error: refs/heads/branch-symbolic-1: badSymrefTarget: point to target outside gitdir
    ++	error: refs/heads/branch-symbolic-2: badSymrefTarget: points to ref outside the refs directory
    ++	error: refs/heads/branch-symbolic-3: badSymrefTarget: points to refname with invalid format
    ++	error: refs/tags/tag-symbolic-1: badSymrefTarget: points to refname with invalid format
    ++	error: refs/tags/tag-symbolic-2: badSymrefTarget: points to the directory
    ++	warning: refs/heads/branch-symbolic-1: symlinkRef: use deprecated symbolic link for symref
    ++	warning: refs/heads/branch-symbolic-2: symlinkRef: use deprecated symbolic link for symref
    ++	warning: refs/heads/branch-symbolic-3: symlinkRef: use deprecated symbolic link for symref
    ++	warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref
    ++	warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
    ++	warning: refs/tags/tag-symbolic-2: symlinkRef: use deprecated symbolic link for symref
    ++	EOF
    ++	sort err >sorted_err &&
    ++	test_cmp expect sorted_err
    ++'
     +
      test_done
Signature · 1 line
2.46.0
author
#65 lore raw

PATCH v3 1/4 ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use " {0} " to express that we are 0-initializing everything. In order to be align with the the codebase, initialize "fsck_ref_report" with zero.

---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 8d6ec9458d..890d0324e1 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = { 0 };
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.46.0
author
#66 lore raw

PATCH v3 2/4 ref: add regular ref content check for files backend

In reply to shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using "files-backend.c::parse_loose_ref_contents", we allow the ref content to end with no newline or to contain some garbages.

Even though we never create such loose refs ourselves, we have accepted such loose refs. So, it is entirely possible that some third-party tools may rely on such loose refs being valid. We should not report an error fsck message at current. But let's notice such a "curiously formatted" loose refs being valid and tell the user our findings, so we can access the possible extent of damage when we tighten the parsing rules in the future.

And it's not suitable to either report a warn fsck message to the user. This is because if the caller set the "strict" field in "fsck_options", fsck warns will be automatically upgraded to errors. We should not allow user to specify the "--strict" flag to upgrade the fsck warnings to errors at current. It might cause compatibility issue which may break the legacy repository. So we add the following two fsck infos to represent the situation where the ref content ends without newline or has garbages:

1. "refMissingNewline(INFO)": A ref does not end with newline. This kind of ref may be considered ERROR in the future.
2. "trailingRefContent(INFO)": A ref has trailing contents. This kind of ref may be considered ERROR in the future.

It may seem that we could not give the user any warnings by creating fsck infos. However, in "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN" and we can still warn the user about these situations when using "git-refs verify" without introducing compatibility issue.

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

In order to tell whether the ref has trailing content, add a new parameter "trailing" to "parse_loose_ref_contents". Then introduce a new function "files_fsck_refs_content" to check the regular refs to enhance the "git-refs verify".

---
 Documentation/fsck-msgids.txt |  11 ++++
 fsck.h                        |   3 +
 refs.c                        |   2 +-
 refs/files-backend.c          |  68 ++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 120 ++++++++++++++++++++++++++++++++++
 6 files changed, 202 insertions(+), 4 deletions(-)
Documentation/fsck-msgids.txt +11 -0
index 68a2801f15..06d045ac48 100644
@@ -19,6 +19,9 @@
 `badParentSha1`::
 	(ERROR) A commit object has a bad parent sha1.
 
+`badRefContent`::
+	(ERROR) A ref has a bad content.
+
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
 
@@ -170,6 +173,14 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A ref does not end with newline. This kind of ref may
+	be considered ERROR in the future.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing contents. This kind of ref may be
+	considered ERROR in the future.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
 
fsck.h +3 -0
index 500b4c04d2..b85072df57 100644
@@ -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) \
@@ -84,6 +85,8 @@ enum fsck_msg_type {
 	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
+	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
 
refs.c +1 -1
index 74de3d3009..5e74881945 100644
@@ -1758,7 +1758,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
 	}
 
 	result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
-					  oid, referent, type, failure_errno);
+					  oid, referent, type, NULL, failure_errno);
 
 done:
 	strbuf_release(&full_path);
refs/files-backend.c +66 -2

98 lines not on this page · raw message

refs/refs-internal.h +1 -1
index 2313c830d8..73b05f971b 100644
@@ -715,7 +715,7 @@ struct ref_store {
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno);
+			     const char **trailing, int *failure_errno);
 
 /*
  * Fill in the generic part of refs and add it to our collection of
t/t0602-reffiles-fsck.sh +120 -0

124 lines not on this page · raw message

Signature · 1 line
2.46.0
#67 lore raw

Re: [PATCH v3 2/4] ref: add regular ref content check for files backend

Quoted 13 lines · shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using "files-backend.c::parse_loose_ref_contents", we allow the ref content to end with no newline or to contain some garbages.

Even though we never create such loose refs ourselves, we have accepted such loose refs. So, it is entirely possible that some third-party tools may rely on such loose refs being valid. We should not report an error fsck message at current. But let's notice such a "curiously formatted" loose refs being valid and tell the user our findings, so we can access the possible extent of damage when we tighten the parsing rules in the future.

Quoted 1 line

And it's not suitable to either report a warn fsck message to the user.

s/to either/either to

Quoted 4 lines

This is because if the caller set the "strict" field in "fsck_options", fsck warns will be automatically upgraded to errors. We should not allow user to specify the "--strict" flag to upgrade the fsck warnings to errors at current.

This is formulated a bit curiously: it reads as if we wanted to limit what the user can do, but what we really want to ensure is that the --strict flag doesn't convert it into an error. So maybe something like this instead of the second sentence:

We don't (yet) want the "--strict" flag that controls this bit to end up generating errors for such weirdly-formatted reference contents, as we first want to assess whether this retroactive tightening will cause issues for any tools out there.

Quoted 1 line

It might cause compatibility issue which may break

s/issue/issues

Quoted 1 line

the legacy repository. So we add the following two fsck infos to

I wouldn't call it "legacy" just yet, as we didn't yet decide whether we're going to make this formatting invalid in the first place. It's rather a test balloon.

Quoted 2 lines

represent the situation where the ref content ends without newline or has garbages:

s/garbages/trailing garbage

Quoted 4 lines

1. "refMissingNewline(INFO)": A ref does not end with newline. This kind of ref may be considered ERROR in the future.
2. "trailingRefContent(INFO)": A ref has trailing contents. This kind of ref may be considered ERROR in the future.

In both cases, "may be considered ERROR" -> "may be considered an error". Also in the actual messages.

Quoted 4 lines

It may seem that we could not give the user any warnings by creating fsck infos. However, in "fsck.c::fsck_vreport", we will convert "FSCK_INFO" to "FSCK_WARN" and we can still warn the user about these situations when using "git-refs verify" without introducing

s/"git-refs verify"/"git refs verify". We don't use dashed builtins nowadays anymore.

Quoted 1 line

compatibility issue.

s/issue/issues

Quoted 4 lines

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

Okay, so this is basically porting over behaviour that git-fsck(1) already has to git refs verify and should thus not cause new issues anywhere. I think it would have made sense to do so in a first step and then introduce the tightened rules in a separate commit.

Will we eventually remove those checks from git-fsck(1) when we adapt it to call git refs verify? If so, we should likely note that in the commit message.

Quoted 4 lines

In order to tell whether the ref has trailing content, add a new parameter "trailing" to "parse_loose_ref_contents". Then introduce a new function "files_fsck_refs_content" to check the regular refs to enhance the "git-refs verify".

This paragraph only re-explains what the diff already tells us, so it can likely be removed.

Quoted 17 lines
---
 Documentation/fsck-msgids.txt |  11 ++++
 fsck.h                        |   3 +
 refs.c                        |   2 +-
 refs/files-backend.c          |  68 ++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 120 ++++++++++++++++++++++++++++++++++
 6 files changed, 202 insertions(+), 4 deletions(-)
Documentation/fsck-msgids.txt +0 -0
index 68a2801f15..06d045ac48 100644

Documentation/fsck-msgids.txt line 24

Quoted 6 lines
Documentation/fsck-msgids.txt +3 -0
@@ -19,6 +19,9 @@
 `badParentSha1`::
 	(ERROR) A commit object has a bad parent sha1.
 
+`badRefContent`::
+	(ERROR) A ref has a bad content.
+

s/a bad content/bad content

Documentation/fsck-msgids.txt line 179

Quoted 11 lines
Documentation/fsck-msgids.txt +4 -0
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
 
@@ -170,6 +173,14 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A ref does not end with newline. This kind of ref may
+	be considered ERROR in the future.
+

I'd reformulate the second sentence to "This will be considered an error in the future". This indicates that we have the intent to tighten this check to any user and would urge them to speak up in case they disagree with such a tightening.

Documentation/fsck-msgids.txt line 182

Quoted 3 lines
Documentation/fsck-msgids.txt +3 -0
+`trailingRefContent`::
+	(INFO) A ref has trailing contents. This kind of ref may be
+	considered ERROR in the future.

Same.

Quoted 18 lines
Documentation/fsck-msgids.txt +14 -0
@@ -3430,6 +3434,65 @@ 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};
+	const char *trailing = NULL;
+	unsigned int type = 0;
+	int failure_errno = 0;
+	struct object_id oid;
+	int ret = 0;

Documentation/fsck-msgids.txt typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3460

Quoted 10 lines
Documentation/fsck-msgids.txt +10 -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 = error_errno(_("%s/%s: unable to read the ref"),
+				  refs_check_dir, iter->relative_path);

We typically have the name of things we read trailing and not leading in error messages. So this should rather be "unable do read ref '%s/%s'".

Documentation/fsck-msgids.txt typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3473

Quoted 13 lines
Documentation/fsck-msgids.txt +13 -0
+		goto cleanup;
+	}
+
+	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
+				     ref_content.buf, &oid, &referent,
+				     &type, &trailing, &failure_errno)) {
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_BAD_REF_CONTENT,
+				      "invalid ref content");
+		goto cleanup;
+	}
+
+	if (!(type & REF_ISSYMREF)) {

Coming back to my comment further up, I guess this whole block here could be introduced in a separate commit. So the first commit introduces the infra to check loose ref contents as an obvious step because we simply port over rules that already exist in git-fsck(1). And the second step could then do this retroactive tightening with the justification you have spelt out in the commit message.

Documentation/fsck-msgids.txt typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3474

Quoted 1 line
Documentation/fsck-msgids.txt +1 -0
+		if (*trailing == '\0') {

if (!*trailing)

Patrick

author
#68 lore raw

Re: [PATCH v3 2/4] ref: add regular ref content check for files backend

Quoted 10 lines · Patrick Steinhardt
Quoted 4 lines

This is because if the caller set the "strict" field in "fsck_options", fsck warns will be automatically upgraded to errors. We should not allow user to specify the "--strict" flag to upgrade the fsck warnings to errors at current.

This is formulated a bit curiously: it reads as if we wanted to limit what the user can do, but what we really want to ensure is that the --strict flag doesn't convert it into an error. So maybe something like this instead of the second sentence:

Quoted 5 lines

We don't (yet) want the "--strict" flag that controls this bit to end up generating errors for such weirdly-formatted reference contents, as we first want to assess whether this retroactive tightening will cause issues for any tools out there.

Thanks, I will improve this in the next version.

Quoted 6 lines
Quoted 1 line

the legacy repository. So we add the following two fsck infos to

I wouldn't call it "legacy" just yet, as we didn't yet decide whether we're going to make this formatting invalid in the first place. It's rather a test balloon.

I agree, we should drop "legacy" here.

Quoted 10 lines
Quoted 4 lines

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

Okay, so this is basically porting over behaviour that git-fsck(1) already has to git refs verify and should thus not cause new issues anywhere. I think it would have made sense to do so in a first step and then introduce the tightened rules in a separate commit.

By reading the whole comments, we'd better create a commit which ports the existing checks to "git refs verify" both for regular refs and symrefs.

So, I will add more commits in the next version with the following sequences:

1. Set up the infrastructure to check the contents for refs.
2. Port existing checks in "git-fsck(1)" to "git refs verify".
3. Introduce the tightened rules.

Quoted 3 lines

Will we eventually remove those checks from git-fsck(1) when we adapt it to call git refs verify? If so, we should likely note that in the commit message.

We should do this, as we have discussed before, "git-fsck(1)" implicitly checks some refs which makes the code hard to understand.

Quoted 6 lines

Coming back to my comment further up, I guess this whole block here could be introduced in a separate commit. So the first commit introduces the infra to check loose ref contents as an obvious step because we simply port over rules that already exist in git-fsck(1). And the second step could then do this retroactive tightening with the justification you have spelt out in the commit message.

Yes, it will be much more clear. So, I should not simply classify the situations by the type of refs.

Thanks,
Jialuo

#69 lore raw

Re: [PATCH v3 2/4] ref: add regular ref content check for files backend

In reply to shejialuo
Quoted 2 lines · shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, when parsing the regular refs for files backend by using

Nit: s/for/in the/

Quoted 2 lines

"files-backend.c::parse_loose_ref_contents", we allow the ref content to end with no newline or to contain some garbages.

The 'no newline' reads a bit odd, perhaps, "we allow the ref's content to end with garbage or without a newline."

Quoted 4 lines

Even though we never create such loose refs ourselves, we have accepted such loose refs. So, it is entirely possible that some third-party tools may rely on such loose refs being valid. We should not report an error fsck message at current. But let's notice such a "curiously formatted"

s/such a/such/ since the next line uses 'refs' in plural form.

Quoted 1 line

loose refs being valid and tell the user our findings, so we can access

s/access/assess

Quoted 3 lines

the possible extent of damage when we tighten the parsing rules in the future.

We could also rewrite the last sentence to make it a little more clearer as "We should notify the users about such 'curiously formatted' loose refs so that adequate care is taken before we decide to tighter the rules in the future."

Quoted 6 lines

And it's not suitable to either report a warn fsck message to the user. This is because if the caller set the "strict" field in "fsck_options", fsck warns will be automatically upgraded to errors. We should not allow user to specify the "--strict" flag to upgrade the fsck warnings to errors at current. It might cause compatibility issue which may break the legacy repository. So we add the following two fsck infos to

I think Patrick touched base here and I agree with his comments.

Quoted 6 lines

represent the situation where the ref content ends without newline or has garbages:

1. "refMissingNewline(INFO)": A ref does not end with newline. This kind of ref may be considered ERROR in the future.
2. "trailingRefContent(INFO)": A ref has trailing contents. This kind of

s/contents/content

Quoted 3 lines

ref may be considered ERROR in the future.

It may seem that we could not give the user any warnings by creating

s/could/would

Quoted 1 line

fsck infos. However, in "fsck.c::fsck_vreport", we will convert

I think we can also rephrase this first sentence a little better, perhaps:

It might appear that we can't provide the user with any warnings by using FSCK_INFO.

Quoted 3 lines

"FSCK_INFO" to "FSCK_WARN" and we can still warn the user about these situations when using "git-refs verify" without introducing compatibility issue.

s/issue/issues

Quoted 4 lines

In current "git-fsck(1)", it will report an error when the ref content is bad, so we should following this to report an error to the user when "parse_loose_ref_contents" fails. And we add a new fsck error message called "badRefContent(ERROR)" to represent that a ref has a bad content.

I would rephrase this a bit, as:

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

[snip]

Quoted 10 lines
refs/files-backend.c +10 -0
+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};
+	const char *trailing = NULL;

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3456

Quoted 10 lines
refs/files-backend.c +10 -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;

Since we iterate over all refs, we don't need to check the target for a symbolic link. So we skip all symbolic links. Makes sense. Would be nice to have a comment here.

[snip]

author
#70 lore raw

Re: [PATCH v3 2/4] ref: add regular ref content check for files backend

On Tue, Sep 10, 2024 at 09:07:15AM -0700, karthik nayak wrote:

[snip]

Quoted 16 lines
Quoted 16 lines
refs/files-backend.c +16 -0
+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};
+	const char *trailing = NULL;
+	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);
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3456

Quoted 4 lines
refs/files-backend.c +4 -0
+	report.path = refname.buf;
+
+	if (S_ISLNK(iter->st.st_mode))
+		goto cleanup;

Since we iterate over all refs, we don't need to check the target for a symbolic link. So we skip all symbolic links. Makes sense. Would be nice to have a comment here.

Today I am handling the reviews, there is a misunderstanding here. It's correct that "We don't need to check the target for a symbolic link". But we do need to check the symbolic links. It might be a symlink symref. In here, we just ignore the implementation and will be implemented in the later patch.

author
#71 lore raw

PATCH v3 3/4 ref: add symref content check for files backend

In reply to shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of teh symref itself.

In order to check the content of the symref, create a function "files_fsck_symref_target". It will first check whether the "referent" is under the "refs/" directory and then we will check the symref contents.

A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'.

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to chceck whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

At last, we need to check whether the referent is the directory. We cannot distinguish whether the "refs/heads/a" is a directory or not by using "check_refname_format". We have already checked bad file type when iterating the "refs/" directory but we ignore the directory. Thus, we need to explicitly add check here.

---
 Documentation/fsck-msgids.txt |   4 ++
 fsck.h                        |   1 +
 refs/files-backend.c          |  81 +++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 117 ++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+)
Documentation/fsck-msgids.txt +4 -0
index 06d045ac48..beb6c4e49e 100644
@@ -28,6 +28,10 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefTarget`::
+	(ERROR) The symref target points outside the ref directory or
+	the name of the symref target is invalid.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index b85072df57..5ea874916d 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_TARGET, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +81 -0

104 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +117 -0

121 lines not on this page · raw message

Signature · 1 line
2.46.0
#72 lore raw

Re: [PATCH v3 3/4] ref: add symref content check for files backend

Quoted 3 lines · shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of teh symref itself.

s/teh/the

Quoted 20 lines

In order to check the content of the symref, create a function "files_fsck_symref_target". It will first check whether the "referent" is under the "refs/" directory and then we will check the symref contents.

A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

Quoted 6 lines

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

In order to provide above checks, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'.

I'd still argue that we should do the same retroactive tightening as we introduce for normal references, also with an INFO level at first. Otherwise we're being inconsistent across the ref types.

Quoted 29 lines

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to chceck whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

At last, we need to check whether the referent is the directory. We cannot distinguish whether the "refs/heads/a" is a directory or not by using "check_refname_format". We have already checked bad file type when iterating the "refs/" directory but we ignore the directory. Thus, we need to explicitly add check here.

---
 Documentation/fsck-msgids.txt |   4 ++
 fsck.h                        |   1 +
 refs/files-backend.c          |  81 +++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 117 ++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+)
Documentation/fsck-msgids.txt +0 -0
index 06d045ac48..beb6c4e49e 100644

Documentation/fsck-msgids.txt line 33

Quoted 6 lines
Documentation/fsck-msgids.txt +3 -0
@@ -28,6 +28,10 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badSymrefTarget`::
+	(ERROR) The symref target points outside the ref directory or
+	the name of the symref target is invalid.

These are two separate error cases, and we even have different code paths raising them. Shouldn't we thus also have two different diagnostic codes for this?

Quoted 29 lines
Documentation/fsck-msgids.txt +0 -0
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index b85072df57..5ea874916d 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_SYMREF_TARGET, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +6 -0
index 0187b85c5f..fef32e607f 100644
@@ -3434,11 +3434,80 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  const char *refs_check_dir,
 				  struct dir_iterator *iter);
 
+/*
+ * Check the symref "referent" and "referent_path". For textual symref,
+ * "referent" would be the content after "refs:".
+ */
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3452

Quoted 10 lines
refs/files-backend.c +10 -0
+				    struct strbuf *referent,
+				    struct strbuf *referent_path)
+{
+	size_t len = referent->len - 1;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(referent->buf, "refs/", &p)) {
+

There's a superfluous newline here.

Also, you never use the value of p, so you can instead use starts_with().

Quoted 25 lines
refs/files-backend.c +25 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	if (referent->buf[referent->len - 1] != '\n') {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+		len++;
+	}
+
+	strbuf_rtrim(referent);
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to refname with invalid format");
+		goto out;
+	}
+
+	if (len != referent->len) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_TRAILING_REF_CONTENT,
+				      "trailing garbage in ref");

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3487

Quoted 10 lines
refs/files-backend.c +10 -0
+	}
+
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */
+	if (lstat(referent_path->buf, &st))
+		goto out;

We may also want to verify that errno == ENOENT here.

Patrick

author
#73 lore raw

Re: [PATCH v3 3/4] ref: add symref content check for files backend

Quoted 8 lines · Patrick Steinhardt
Quoted 3 lines

In order to provide above checks, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'.

I'd still argue that we should do the same retroactive tightening as we introduce for normal references, also with an INFO level at first. Otherwise we're being inconsistent across the ref types.

Actually, for above situations, we will use the same fsck error message ids introduce in [PATCH v3 2/4]. And I think we must refer to this in this commit message.

But it makes me wonder should we use a new commit to introduce these two fsck message ids?

Quoted 8 lines
Quoted 8 lines
Documentation/fsck-msgids.txt +0 -0
index 06d045ac48..beb6c4e49e 100644
@@ -28,6 +28,10 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
Quoted 8 lines

Documentation/fsck-msgids.txt line 33

Quoted 3 lines
Documentation/fsck-msgids.txt +3 -0
+`badSymrefTarget`::
+	(ERROR) The symref target points outside the ref directory or
+	the name of the symref target is invalid.

These are two separate error cases, and we even have different code paths raising them. Shouldn't we thus also have two different diagnostic codes for this?

I agree. I will improve in the next version.

Quoted 8 lines

Documentation/fsck-msgids.txt line 35

Quoted 2 lines
Documentation/fsck-msgids.txt +2 -0
+	if (!skip_prefix(referent->buf, "refs/", &p)) {
+

There's a superfluous newline here.

Also, you never use the value of p, so you can instead use starts_with().

Thanks, actually I have searched the code with "is_prefix". Well, I didn't think about "starts_<>".

Quoted 11 lines

Documentation/fsck-msgids.txt

Quoted 8 lines
Documentation/fsck-msgids.txt +8 -0
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */
+	if (lstat(referent_path->buf, &st))
+		goto out;

We may also want to verify that errno == ENOENT here.

I agree, if "errno != ENOENT", we should report to the user about this ref-unrelated failure.

Thanks,
Jialuo

#74 lore raw

Re: [PATCH v3 3/4] ref: add symref content check for files backend

In reply to shejialuo

shejialuo <[email protected]> writes:

[snip]

Quoted 3 lines

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to chceck whether the trimmed referent format is valid. If not, we will

s/chceck/check

Quoted 6 lines

report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

At last, we need to check whether the referent is the directory. We

s/is the/is a/

Quoted 1 line

cannot distinguish whether the "refs/heads/a" is a directory or not by

It would be a little clearer if we say

We cannot distinguish whether a given reference like 'refs/heads/a' is a file or a directory.

Quoted 4 lines

using "check_refname_format". We have already checked bad file type when iterating the "refs/" directory but we ignore the directory. Thus, we need to explicitly add check here.

[snip]

Quoted 28 lines
refs/files-backend.c +28 -0
+/*
+ * Check the symref "referent" and "referent_path". For textual symref,
+ * "referent" would be the content after "refs:".
+ */
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    struct strbuf *referent,
+				    struct strbuf *referent_path)
+{
+	size_t len = referent->len - 1;
+	const char *p = NULL;
+	struct stat st;
+	int ret = 0;
+
+	if (!skip_prefix(referent->buf, "refs/", &p)) {
+
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	if (referent->buf[referent->len - 1] != '\n') {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+		len++;
+	}

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3474

Quoted 10 lines
refs/files-backend.c +10 -0
+
+	strbuf_rtrim(referent);
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to refname with invalid format");
+		goto out;
+	}
+
+	if (len != referent->len) {

Would this work with a symref containing:

ref: refs/heads/feature\ngarbage\n

Since we check last character and rtrim, wouldn't this bypass our checks? Isn't it better to find the first \n and check if the index < referent->len?

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3485

Quoted 11 lines
refs/files-backend.c +11 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_TRAILING_REF_CONTENT,
+				      "trailing garbage in ref");
+	}
+
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */

I think the common terminology for this is 'dangling symref'. Perhaps we could shorten this to simply say:

Dangling symrefs are common and so we don't report them.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3490

Quoted 5 lines
refs/files-backend.c +5 -0
+	if (lstat(referent_path->buf, &st))
+		goto out;
+
+	/*
+	 * We cannot distinguish whether "refs/heads/a" is directory or nots by

s/is/is a/
s/nots/not/

Quoted 14 lines
refs/files-backend.c +14 -0
+	 * using "check_refname_format(referent->buf, 0)". Instead, we need to
+	 * check the file type of the target.
+	 */
+	if (S_ISDIR(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to the directory");
+		goto out;
+	}
+
+out:
+	return ret;
+}
+

[snip]

author
#75 lore raw

Re: [PATCH v3 3/4] ref: add symref content check for files backend

On Tue, Sep 10, 2024 at 03:19:49PM -0700, karthik nayak wrote:

[snip]

Quoted 19 lines
Quoted 6 lines
refs/files-backend.c +6 -0
+	if (referent->buf[referent->len - 1] != '\n') {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+		len++;
+	}

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3474

Quoted 10 lines
refs/files-backend.c +10 -0
+
+	strbuf_rtrim(referent);
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      "points to refname with invalid format");
+		goto out;
+	}
+
+	if (len != referent->len) {

Would this work with a symref containing:

Quoted 6 lines

ref: refs/heads/feature\ngarbage\n

Since we check last character and rtrim, wouldn't this bypass our checks? Isn't it better to find the first \n and check if the index < referent->len?

We will check the above example by "check_refname_format". It will report the following message:

error: ... : badSymrefTarget: points to refname with invalid format

From the context, I guess you suggest that we should report there is a trailing garbage in the ref. However, for the above situation, we should report an error which is align with the behavior of the "git-fsck(1)".

So there is no need to check whether there is a trailing garbage when we encounter an error.

And we cannot use this way, for example:

ref: refs/heads/feature \n

If we find the first '\n' index. In this example, index will be equal to "referent->len". And we totally ignore this case.

Quoted 15 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3485

Quoted 11 lines
refs/files-backend.c +11 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_TRAILING_REF_CONTENT,
+				      "trailing garbage in ref");
+	}
+
+	/*
+	 * Missing target should not be treated as any error worthy event and
+	 * not even warn. It is a common case that a symbolic ref points to a
+	 * ref that does not exist yet. If the target ref does not exist, just
+	 * skip the check for the file type.
+	 */

I think the common terminology for this is 'dangling symref'. Perhaps we could shorten this to simply say:

Quoted 2 lines

Dangling symrefs are common and so we don't report them.

Thanks, I will improve this in the next version.

author
#76 lore raw

PATCH v3 4/4 ref: add symlink ref content check for files backend

In reply to shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

We firstly use the "strbuf_add_real_path" to resolve the symlink and get the absolute path "referent_path" which the symlink ref points to. Then we can get the absolute path "abs_gitdir" of the "gitdir". By combining "referent_path" and "abs_gitdir", we can extract the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

Because we are going to drop support for "core.prefersymlinkrefs", add a new fsck message "symlinkRef" to let the user be aware of this information.

---
 Documentation/fsck-msgids.txt |  5 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 68 +++++++++++++++++++-----
 t/t0602-reffiles-fsck.sh      | 97 +++++++++++++++++++++++++++++++++++
 4 files changed, 157 insertions(+), 14 deletions(-)
Documentation/fsck-msgids.txt +5 -0
index beb6c4e49e..9e8e1ac7f0 100644
@@ -181,6 +181,11 @@
 	(INFO) A ref does not end with newline. This kind of ref may
 	be considered ERROR in the future.
 
+`symlinkRef`::
+	(INFO) A symref uses the symbolic link. This kind of symref may
+	be considered ERROR in the future when totally dropping the
+	symlink support.
+
 `trailingRefContent`::
 	(INFO) A ref has trailing contents. This kind of ref may be
 	considered ERROR in the future.
fsck.h +1 -0
index 5ea874916d..1c6f750812 100644
@@ -87,6 +87,7 @@ enum fsck_msg_type {
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
 	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(SYMLINK_REF, INFO) \
 	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
refs/files-backend.c +54 -14

152 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +97 -0

101 lines not on this page · raw message

Signature · 1 line
2.46.0
#77 lore raw

Re: [PATCH v3 4/4] ref: add symlink ref content check for files backend

Quoted 13 lines · shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

We firstly use the "strbuf_add_real_path" to resolve the symlink and get the absolute path "referent_path" which the symlink ref points to. Then we can get the absolute path "abs_gitdir" of the "gitdir". By combining "referent_path" and "abs_gitdir", we can extract the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

Quoted 3 lines

Because we are going to drop support for "core.prefersymlinkrefs", add a new fsck message "symlinkRef" to let the user be aware of this information.

I don't we fully decided to drop support for symrefs via symbolic links yet, so this is a tad too strong of a statement. I'd rather say that we consider deprecating it in the future, but first need to asses whether they may still be used.

Also, didn't we say that we'd want to remove support for _writing_ symbolic links, but not for reading them? Not a 100% sure though.

Quoted 18 lines
refs/files-backend.c +3 -4
@@ -1961,13 +1965,12 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
 
 	if (ret)
 		fprintf(stderr, "no symlink - falling back to symbolic ref\n");
-#endif
 	return ret;
 }
+#endif
 
-static int create_symref_lock(struct files_ref_store *refs,
-			      struct ref_lock *lock, const char *refname,
-			      const char *target, struct strbuf *err)
+static int create_symref_lock(struct ref_lock *lock, const char *target,
+			      struct strbuf *err)
 {
 	if (!fdopen_lock_file(&lock->lk, "w")) {
 		strbuf_addf(err, "unable to fdopen %s: %s",

refs/files-backend.c static int lock_ref_for_update(struct files_ref_store *refs, · line 2592

Quoted 9 lines
refs/files-backend.c +1 -2
@@ -2583,8 +2586,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
 	}
 
 	if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-		if (create_symref_lock(refs, lock, update->refname,
-				       update->new_target, err)) {
+		if (create_symref_lock(lock, update->new_target, err)) {
 			ret = TRANSACTION_GENERIC_ERROR;
 			goto out;
 		}

Why does the writing side need to change?

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3523

Quoted 9 lines
refs/files-backend.c +2 -0
@@ -3509,9 +3516,11 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 {
 	struct strbuf referent_path = STRBUF_INIT;
 	struct strbuf ref_content = STRBUF_INIT;
+	struct strbuf abs_gitdir = STRBUF_INIT;
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
 	struct fsck_ref_report report = {0};
+	unsigned int symbolic_link = 0;

This variable isn't used, as both code paths that end up using it could just statically set it to 1 or 0.

Quoted 32 lines
refs/files-backend.c +24 -1
 	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
@@ -3521,8 +3530,37 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	strbuf_addf(&refname, "%s/%s", refs_check_dir, iter->relative_path);
 	report.path = refname.buf;
 
-	if (S_ISLNK(iter->st.st_mode))
+	if (S_ISLNK(iter->st.st_mode)) {
+		const char* relative_referent_path;
+
+		symbolic_link = 1;
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_SYMLINK_REF,
+				      "use deprecated symbolic link for symref");
+
+		strbuf_add_absolute_path(&abs_gitdir, ref_store->gitdir);
+		strbuf_normalize_path(&abs_gitdir);
+		if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1]))
+			strbuf_addch(&abs_gitdir, '/');
+
+		strbuf_add_real_path(&referent_path, iter->path.buf);
+
+		if (!skip_prefix(referent_path.buf,
+				 abs_gitdir.buf,
+				 &relative_referent_path)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_BAD_SYMREF_TARGET,
+					      "point to target outside gitdir");
+			goto cleanup;
+		}
+

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3566

Quoted 10 lines
refs/files-backend.c +6 -0
+		strbuf_addstr(&referent, relative_referent_path);
+		ret = files_fsck_symref_target(o, &report,
+					       &referent, &referent_path,
+					       symbolic_link);
+
 		goto cleanup;
+	}
 
 	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
 		ret = error_errno(_("%s/%s: unable to read the ref"),

Patrick

author
#78 lore raw

Re: [PATCH v3 4/4] ref: add symlink ref content check for files backend

Quoted 9 lines · Patrick Steinhardt
Quoted 3 lines

Because we are going to drop support for "core.prefersymlinkrefs", add a new fsck message "symlinkRef" to let the user be aware of this information.

I don't we fully decided to drop support for symrefs via symbolic links yet, so this is a tad too strong of a statement. I'd rather say that we consider deprecating it in the future, but first need to asses whether they may still be used.

Yes, that will be much better.

Quoted 3 lines

Also, didn't we say that we'd want to remove support for _writing_ symbolic links, but not for reading them? Not a 100% sure though.

I have re-read the Junio's patch about the breaking change. We will drop the support for writing. But for reading we may or may not. I will improve this in the next version.

Quoted 10 lines

refs/files-backend.c static int lock_ref_for_update(struct files_ref_store *refs, · line 2592

Quoted 7 lines
refs/files-backend.c +1 -2
 	if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-		if (create_symref_lock(refs, lock, update->refname,
-				       update->new_target, err)) {
+		if (create_symref_lock(lock, update->new_target, err)) {
 			ret = TRANSACTION_GENERIC_ERROR;
 			goto out;
 		}

Why does the writing side need to change?

I squash two patches provided by Junio to sync with the "master" branch to make sure the build could be passed. This is because Peff has introduced the "UNUSED" check when building.

So we could just ignore this part.

Thanks,
Jialuo

author
#79 lore raw

v4 cover add ref content check for files backend

In reply to shejialuo

Hi All:

This version handles some minor problems mainly focus at the improving commit messages, comments and some minor problems.

1. Split [PATCH v3 2/4] into two commits [PATCH v4 2/5] and [PATCH v4 3/5]. [PATCH v4 2/5] integrates "git-fsck(1)"'s check and [PATCH v4 3/5] tightens rules to check the refs with trailing garbage and refs without newline.

2. Handle a lot of typo errors in original [PATCH v3 2/4]. And improve the fsck-msgids documentation.

3. Improve [PATCH v4 4/5]'s commit message to first introduce the tighten rules to be consistent with the [PATCH v4 3/5].

4. Remove "badSymrefTarget(ERROR)" fsck message. Add three new messages to be more specific:

1. badReferentFiletype(ERROR): The referent of a symref has a bad file type.

2. badReferentName(ERROR): The referent name of a symref is invalid.

3. escapeReferent(ERROR): The referent of a symref is outside the ref directory

5. Handle typos and some minor problems.

Because I add more commits, I provide the "--interdiff" here to make the reviewer's life easier.

However, because I have not merged the latest ci fixup, so I cannot verify some jobs in CIs. May need the help from Junio to verify.

Thanks,
Jialuo

shejialuo (5):
ref: initialize "fsck_ref_report" with zero
ref: port git-fsck(1) regular refs check for files backend
ref: add more strict checks for regular refs
ref: add symref content check for files backend
ref: add symlink ref content check for files backend

 Documentation/fsck-msgids.txt |  25 +++
 fsck.h                        |   7 +
 refs.c                        |   2 +-
 refs/files-backend.c          | 202 +++++++++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 334 ++++++++++++++++++++++++++++++++++
 6 files changed, 560 insertions(+), 12 deletions(-)

Interdiff against v3:

Documentation/fsck-msgids.txt +13 -8
index 9e8e1ac7f0..31626e765b 100644
@@ -20,7 +20,7 @@
 	(ERROR) A commit object has a bad parent sha1.
 
 `badRefContent`::
-	(ERROR) A ref has a bad content.
+	(ERROR) A ref has bad content.
 
 `badRefFiletype`::
 	(ERROR) A ref has a bad file type.
@@ -28,9 +28,11 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
-`badSymrefTarget`::
-	(ERROR) The symref target points outside the ref directory or
-	the name of the symref target is invalid.
+`badReferentFiletype`::
+	(ERROR) The referent of a symref has a bad file type.
+
+`badReferentName`::
+	(ERROR) The referent name of a symref is invalid.
 
 `badTagName`::
 	(INFO) A tag has an invalid format.
@@ -53,6 +55,9 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
+`escapeReferent`::
+	(ERROR) The referent of a symref is outside the "ref" directory.
+
 `extraHeaderEntry`::
 	(IGNORE) Extra headers found after `tagger`.
 
@@ -178,8 +183,8 @@
 	(WARN) Tree contains entries pointing to a null sha1.
 
 `refMissingNewline`::
-	(INFO) A ref does not end with newline. This kind of ref may
-	be considered ERROR in the future.
+	(INFO) A ref does not end with newline. This will be
+	considered an error in the future.
 
 `symlinkRef`::
 	(INFO) A symref uses the symbolic link. This kind of symref may
@@ -187,8 +192,8 @@
 	symlink support.
 
 `trailingRefContent`::
-	(INFO) A ref has trailing contents. This kind of ref may be
-	considered ERROR in the future.
+	(INFO) A ref has trailing content. This will be
+	considered an error in the future.
 
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
fsck.h +3 -1
index 1c6f750812..b72ee632a4 100644
@@ -34,12 +34,14 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
-	FUNC(BAD_SYMREF_TARGET, ERROR) \
+	FUNC(BAD_REFERENT_FILETYPE, ERROR) \
+	FUNC(BAD_REFERENT_NAME, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
 	FUNC(BAD_TYPE, ERROR) \
 	FUNC(DUPLICATE_ENTRIES, ERROR) \
+	FUNC(ESCAPE_REFERENT, ERROR) \
 	FUNC(MISSING_AUTHOR, ERROR) \
 	FUNC(MISSING_COMMITTER, ERROR) \
 	FUNC(MISSING_EMAIL, ERROR) \
refs/files-backend.c +18 -21
index 2a1b952f0d..c511deb509 100644
@@ -3449,14 +3449,12 @@ static int files_fsck_symref_target(struct fsck_options *o,
 				    unsigned int symbolic_link)
 {
 	size_t len = referent->len - 1;
-	const char *p = NULL;
 	struct stat st;
 	int ret = 0;
 
-	if (!skip_prefix(referent->buf, "refs/", &p)) {
-
+	if (!starts_with(referent->buf, "refs/")) {
 		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      FSCK_MSG_ESCAPE_REFERENT,
 				      "points to ref outside the refs directory");
 		goto out;
 	}
@@ -3473,7 +3471,7 @@ static int files_fsck_symref_target(struct fsck_options *o,
 
 	if (check_refname_format(referent->buf, 0)) {
 		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      FSCK_MSG_BAD_REFERENT_NAME,
 				      "points to refname with invalid format");
 		goto out;
 	}
@@ -3485,22 +3483,24 @@ static int files_fsck_symref_target(struct fsck_options *o,
 	}
 
 	/*
-	 * Missing target should not be treated as any error worthy event and
-	 * not even warn. It is a common case that a symbolic ref points to a
-	 * ref that does not exist yet. If the target ref does not exist, just
-	 * skip the check for the file type.
+	 * Dangling symrefs are common and so we don't report them.
 	 */
-	if (lstat(referent_path->buf, &st))
+	if (lstat(referent_path->buf, &st)) {
+		if (errno != ENOENT) {
+			ret = error_errno(_("unable to stat '%s'"),
+					  referent_path->buf);
+		}
 		goto out;
+	}
 
 	/*
-	 * We cannot distinguish whether "refs/heads/a" is directory or nots by
+	 * We cannot distinguish whether "refs/heads/a" is a directory or not by
 	 * using "check_refname_format(referent->buf, 0)". Instead, we need to
 	 * check the file type of the target.
 	 */
 	if (S_ISDIR(st.st_mode)) {
 		ret = fsck_report_ref(o, report,
-				      FSCK_MSG_BAD_SYMREF_TARGET,
+				      FSCK_MSG_BAD_REFERENT_FILETYPE,
 				      "points to the directory");
 		goto out;
 	}
@@ -3520,7 +3520,6 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
 	struct fsck_ref_report report = {0};
-	unsigned int symbolic_link = 0;
 	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
@@ -3533,7 +3532,6 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	if (S_ISLNK(iter->st.st_mode)) {
 		const char* relative_referent_path;
 
-		symbolic_link = 1;
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_SYMLINK_REF,
 				      "use deprecated symbolic link for symref");
@@ -3549,21 +3547,20 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 				 abs_gitdir.buf,
 				 &relative_referent_path)) {
 			ret = fsck_report_ref(o, &report,
-					      FSCK_MSG_BAD_SYMREF_TARGET,
+					      FSCK_MSG_ESCAPE_REFERENT,
 					      "point to target outside gitdir");
 			goto cleanup;
 		}
 
 		strbuf_addstr(&referent, relative_referent_path);
 		ret = files_fsck_symref_target(o, &report,
-					       &referent, &referent_path,
-					       symbolic_link);
+					       &referent, &referent_path, 1);
 
 		goto cleanup;
 	}
 
 	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
-		ret = error_errno(_("%s/%s: unable to read the ref"),
+		ret = error_errno(_("unable to read ref '%s/%s'"),
 				  refs_check_dir, iter->relative_path);
 		goto cleanup;
 	}
@@ -3578,14 +3575,14 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	}
 
 	if (!(type & REF_ISSYMREF)) {
-		if (*trailing == '\0') {
+		if (!*trailing) {
 			ret = fsck_report_ref(o, &report,
 					      FSCK_MSG_REF_MISSING_NEWLINE,
 					      "missing newline");
 			goto cleanup;
 		}
 
-		if (*trailing != '\n' || (*(trailing + 1) != '\0')) {
+		if (*trailing != '\n' || *(trailing + 1)) {
 			ret = fsck_report_ref(o, &report,
 					      FSCK_MSG_TRAILING_REF_CONTENT,
 					      "trailing garbage in ref");
@@ -3602,7 +3599,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		ret = files_fsck_symref_target(o, &report,
 					       &referent,
 					       &referent_path,
-					       symbolic_link);
+					       0);
 	}
 
 cleanup:
t/t0602-reffiles-fsck.sh +16 -16
index e735816d5b..7c3579705f 100755
@@ -268,7 +268,7 @@ test_expect_success 'textual symref content should be checked (individual)' '
 	printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/branch-bad-1: badSymrefTarget: points to refname with invalid format
+	error: refs/heads/branch-bad-1: badReferentName: points to refname with invalid format
 	EOF
 	rm $branch_dir_prefix/branch-bad-1 &&
 	test_cmp expect err &&
@@ -276,7 +276,7 @@ test_expect_success 'textual symref content should be checked (individual)' '
 	printf "ref: reflogs/heads/main\n" >$branch_dir_prefix/branch-bad-2 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/branch-bad-2: badSymrefTarget: points to ref outside the refs directory
+	error: refs/heads/branch-bad-2: escapeReferent: points to ref outside the refs directory
 	EOF
 	rm $branch_dir_prefix/branch-bad-2 &&
 	test_cmp expect err &&
@@ -284,7 +284,7 @@ test_expect_success 'textual symref content should be checked (individual)' '
 	printf "ref: refs/heads/a\n" >$branch_dir_prefix/branch-bad-3 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/branch-bad-3: badSymrefTarget: points to the directory
+	error: refs/heads/branch-bad-3: badReferentFiletype: points to the directory
 	EOF
 	rm $branch_dir_prefix/branch-bad-3 &&
 	test_cmp expect err
@@ -311,9 +311,9 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
 
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/branch-bad-1: badSymrefTarget: points to refname with invalid format
-	error: refs/heads/branch-bad-2: badSymrefTarget: points to ref outside the refs directory
-	error: refs/heads/branch-bad-3: badSymrefTarget: points to the directory
+	error: refs/heads/branch-bad-1: badReferentName: points to refname with invalid format
+	error: refs/heads/branch-bad-2: escapeReferent: points to ref outside the refs directory
+	error: refs/heads/branch-bad-3: badReferentFiletype: points to the directory
 	warning: refs/heads/a/b/branch-complicated: refMissingNewline: missing newline
 	warning: refs/heads/a/b/branch-complicated: trailingRefContent: trailing garbage in ref
 	warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: missing newline
@@ -347,7 +347,7 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (individu
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
 	warning: refs/heads/branch-symbolic-1: symlinkRef: use deprecated symbolic link for symref
-	error: refs/heads/branch-symbolic-1: badSymrefTarget: point to target outside gitdir
+	error: refs/heads/branch-symbolic-1: escapeReferent: point to target outside gitdir
 	EOF
 	rm $branch_dir_prefix/branch-symbolic-1 &&
 	test_cmp expect err &&
@@ -356,7 +356,7 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (individu
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
 	warning: refs/heads/branch-symbolic-2: symlinkRef: use deprecated symbolic link for symref
-	error: refs/heads/branch-symbolic-2: badSymrefTarget: points to ref outside the refs directory
+	error: refs/heads/branch-symbolic-2: escapeReferent: points to ref outside the refs directory
 	EOF
 	rm $branch_dir_prefix/branch-symbolic-2 &&
 	test_cmp expect err &&
@@ -365,7 +365,7 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (individu
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
 	warning: refs/heads/branch-symbolic-3: symlinkRef: use deprecated symbolic link for symref
-	error: refs/heads/branch-symbolic-3: badSymrefTarget: points to refname with invalid format
+	error: refs/heads/branch-symbolic-3: badReferentName: points to refname with invalid format
 	EOF
 	rm $branch_dir_prefix/branch-symbolic-3 &&
 	test_cmp expect err &&
@@ -374,7 +374,7 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (individu
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
 	warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref
-	error: refs/tags/tag-symbolic-1: badSymrefTarget: points to refname with invalid format
+	error: refs/tags/tag-symbolic-1: badReferentName: points to refname with invalid format
 	EOF
 	rm $tag_dir_prefix/tag-symbolic-1 &&
 	test_cmp expect err &&
@@ -383,7 +383,7 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (individu
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
 	warning: refs/tags/tag-symbolic-2: symlinkRef: use deprecated symbolic link for symref
-	error: refs/tags/tag-symbolic-2: badSymrefTarget: points to the directory
+	error: refs/tags/tag-symbolic-2: badReferentFiletype: points to the directory
 	EOF
 	rm $tag_dir_prefix/tag-symbolic-2 &&
 	test_cmp expect err
@@ -407,11 +407,11 @@ test_expect_success SYMLINKS 'symlink symref content should be checked (aggregat
 
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/branch-symbolic-1: badSymrefTarget: point to target outside gitdir
-	error: refs/heads/branch-symbolic-2: badSymrefTarget: points to ref outside the refs directory
-	error: refs/heads/branch-symbolic-3: badSymrefTarget: points to refname with invalid format
-	error: refs/tags/tag-symbolic-1: badSymrefTarget: points to refname with invalid format
-	error: refs/tags/tag-symbolic-2: badSymrefTarget: points to the directory
+	error: refs/heads/branch-symbolic-1: escapeReferent: point to target outside gitdir
+	error: refs/heads/branch-symbolic-2: escapeReferent: points to ref outside the refs directory
+	error: refs/heads/branch-symbolic-3: badReferentName: points to refname with invalid format
+	error: refs/tags/tag-symbolic-1: badReferentName: points to refname with invalid format
+	error: refs/tags/tag-symbolic-2: badReferentFiletype: points to the directory
 	warning: refs/heads/branch-symbolic-1: symlinkRef: use deprecated symbolic link for symref
 	warning: refs/heads/branch-symbolic-2: symlinkRef: use deprecated symbolic link for symref
 	warning: refs/heads/branch-symbolic-3: symlinkRef: use deprecated symbolic link for symref
Signature · 1 line
2.46.0
author
#80 lore raw

PATCH v4 1/5 ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use " {0} " to express that we are 0-initializing everything. In order to be align with the the codebase, initialize "fsck_ref_report" with zero.

---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 8d6ec9458d..890d0324e1 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = { 0 };
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.46.0
#81 lore raw

Re: [PATCH v4 1/5] ref: initialize "fsck_ref_report" with zero

Quoted 8 lines · shejialuo

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use " {0} " to express

" {0} " -> "{ 0 }"

Quoted 1 line

that we are 0-initializing everything. In order to be align with the the

"be align with the the" -> "align with the"

Quoted 1 line

codebase, initialize "fsck_ref_report" with zero.

Both I'll amend in-place so no need to reroll just for these.

Thanks.

Quoted 21 lines
---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 8d6ec9458d..890d0324e1 100644
@@ -3446,7 +3446,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = { 0 };
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
author
#82 lore raw

PATCH v4 2/5 ref: port git-fsck(1) regular refs check for files backend

In reply to shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, we have already set up the infrastructure of the ref consistency checks. We need to port original checks from "git-fsck(1)". Thus, we could clean the "git-fsck(1)" code by removing these implicit checks.

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

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 43 +++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 60 +++++++++++++++++++++++++++++++++++
 4 files changed, 107 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index 68a2801f15..22c385ea22 100644
@@ -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.
 
fsck.h +1 -0
index 500b4c04d2..0d99a87911 100644
@@ -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) \
refs/files-backend.c +43 -0

55 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +60 -0

64 lines not on this page · raw message

Signature · 1 line
2.46.0
#83 lore raw

Re: [PATCH v4 2/5] ref: port git-fsck(1) regular refs check for files backend

Quoted 5 lines · shejialuo

We implicitly rely on "git-fsck(1)" to check the consistency of regular refs. However, we have already set up the infrastructure of the ref consistency checks. We need to port original checks from "git-fsck(1)". Thus, we could clean the "git-fsck(1)" code by removing these implicit checks.

The above reads as if you are, in preparation to "port" the checks we have in "fsck" to elsewhere (presumably to "refs verify"), you are removing the checks that _will_ become redundant from "fsck".

But that does not seem to be what is happening. Let me try to paraphrase, in order to check my understanding of what you wanted to say:

"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".

If we fail to achieve the "a single unified code to check called by both fsck and refs-verify" at the end of this series, and instead end up with duplicated code that implements the checks in two separate code, risking them to be slightly different and drift away over time from each other, that is fine, as long as our intention is to continue the effort for unification in a follow up series.

But such a plan needs to be spelled out.

Quoted 17 lines
refs/files-backend.c +9 -0
index 890d0324e1..b1ed2e5c04 100644
@@ -3430,6 +3430,48 @@ 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};

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3451

Quoted 10 lines
refs/files-backend.c +10 -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;

"symbolic links are OK" for now. We'll add sanity checks for them in later steps. OK.

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3455

Quoted 3 lines
refs/files-backend.c +3 -0
+	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
+		ret = error_errno(_("unable to read ref '%s/%s'"),
+				  refs_check_dir, iter->relative_path);

Is there a reason why we cannot to use report.path aka refname.buf, and instead we have to recompute the same path again?

Should this error be propagated back to the caller, not just to the end-user, by a call to fsck_report_ref(), like you do for a ref file that has questionable contents? If ref iteration (like for-each-ref) claims there is this ref, and you cannot read its value when you try to use it, it is just as bad as having a loose ref file that has unusable contents, isn't it?

It is a separate matter if such a failure mode deserves its own error code (FSCK_MSG_UNREADABLE_REF) or can be rolled into the same FSCK_MSG_BAD_REF_CONTENT. I can see arguments for both sides and offhand have no strong preference either way.

Thanks.

Quoted 18 lines
refs/files-backend.c +18 -0
+		goto cleanup;
+	}
+
+	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
+				     ref_content.buf, &oid, &referent,
+				     &type, &failure_errno)) {
+		ret = fsck_report_ref(o, &report,
+				      FSCK_MSG_BAD_REF_CONTENT,
+				      "invalid ref content");
+		goto cleanup;
+	}
+
+cleanup:
+	strbuf_release(&refname);
+	strbuf_release(&ref_content);
+	strbuf_release(&referent);
+	return ret;
+}
author
#84 lore raw

Re: [PATCH v4 2/5] ref: port git-fsck(1) regular refs check for files backend

On Wed, Sep 18, 2024 at 11:59:45AM -0700, Junio C Hamano wrote:

[snip]

Quoted 4 lines

The above reads as if you are, in preparation to "port" the checks we have in "fsck" to elsewhere (presumably to "refs verify"), you are removing the checks that _will_ become redundant from "fsck".

Quoted 9 lines

But that does not seem to be what is happening. Let me try to paraphrase, in order to check my understanding of what you wanted to say:

"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".

Thanks, I have re-read my words, I did not explain this thing well.

Quoted 7 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3455

Quoted 3 lines
refs/files-backend.c +3 -0
+	if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) {
+		ret = error_errno(_("unable to read ref '%s/%s'"),
+				  refs_check_dir, iter->relative_path);

Is there a reason why we cannot to use report.path aka refname.buf, and instead we have to recompute the same path again?

Thanks for pointing out this, because this part I wrote a long time ago and I think it's unrelated to the fsck part. So, I forgot to change.

Quoted 7 lines

Should this error be propagated back to the caller, not just to the end-user, by a call to fsck_report_ref(), like you do for a ref file that has questionable contents? If ref iteration (like for-each-ref) claims there is this ref, and you cannot read its value when you try to use it, it is just as bad as having a loose ref file that has unusable contents, isn't it?

I agree. The initial motivation for this design is that I think this is OS-specific issue (It may be read successfully in the next time). So, I don't put it into the fsck part. But It make senses that we should report this.

Quoted 5 lines

It is a separate matter if such a failure mode deserves its own error code (FSCK_MSG_UNREADABLE_REF) or can be rolled into the same FSCK_MSG_BAD_REF_CONTENT. I can see arguments for both sides and offhand have no strong preference either way.

We could just use "FSCK_MSG_BAD_REF_CONTENT" and add a message "cannot open this file". I guess this should be enough.

author
#85 lore raw

PATCH v4 3/5 ref: add more strict checks for regular refs

In reply to shejialuo

We have already used "parse_loose_ref_contents" function to check whether the ref content is valid in files backend. However, by using "parse_loose_ref_contents", we allow the ref's content to end with garbage or without a newline.

Even though we never create such loose refs ourselves, we have accepted such loose refs. So, it is entirely possible that some third-party tools may rely on such loose refs being valid. We should not report an error fsck message at current. We should notify the users about such "curiously formatted" loose refs so that adequate care is taken before we decide to tighten the rules in the future.

And it's not suitable either to report a warn fsck message to the user. We don't yet want the "--strict" flag that controls this bit to end up generating errors for such weirdly-formatted reference contents, as we first want to assess whether this retroactive tightening will cause issues for any tools out there. It may cause compatibility issues which may break the repository. So we add the following two fsck infos to represent the situation where the ref content ends without newline or has trailing garbages:

1. refMissingNewline(INFO): A ref does not end with newline. This will be considered an error in the future.
2. trailingRefContent(INFO): A ref has trailing content. This will be considered an error in the future.

It might appear that we can't provide the user with any warnings by using FSCK_INFO. However, in "fsck.c::fsck_vreport", we will convert FSCK_INFO to FSCK_WARN and we can still warn the user about these situations when using "git refs verify" without introducing compatibility issues.

---
 Documentation/fsck-msgids.txt |  8 +++++
 fsck.h                        |  2 ++
 refs.c                        |  2 +-
 refs/files-backend.c          | 27 ++++++++++++++--
 refs/refs-internal.h          |  2 +-
 t/t0602-reffiles-fsck.sh      | 60 +++++++++++++++++++++++++++++++++++
 6 files changed, 96 insertions(+), 5 deletions(-)
Documentation/fsck-msgids.txt +8 -0
index 22c385ea22..8827137ef0 100644
@@ -173,6 +173,14 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A ref does not end with newline. This will be
+	considered an error in the future.
+
+`trailingRefContent`::
+	(INFO) A ref has trailing content. This will be
+	considered an error in the future.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
 
fsck.h +2 -0
index 0d99a87911..b85072df57 100644
@@ -85,6 +85,8 @@ enum fsck_msg_type {
 	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
+	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
 
refs.c +1 -1
index 74de3d3009..5e74881945 100644
@@ -1758,7 +1758,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
 	}
 
 	result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
-					  oid, referent, type, failure_errno);
+					  oid, referent, type, NULL, failure_errno);
 
 done:
 	strbuf_release(&full_path);
refs/files-backend.c +24 -3
index b1ed2e5c04..df4ce270ae 100644
@@ -560,7 +560,7 @@ static int read_ref_internal(struct ref_store *ref_store, const char *refname,
 	buf = sb_contents.buf;
 
 	ret = parse_loose_ref_contents(ref_store->repo->hash_algo, buf,
-				       oid, referent, type, &myerr);
+				       oid, referent, type, NULL, &myerr);
 
 out:
 	if (ret && !myerr)
@@ -597,7 +597,7 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno)
+			     const char **trailing, int *failure_errno)
 {
 	const char *p;
 	if (skip_prefix(buf, "ref:", &buf)) {
@@ -619,6 +619,10 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
 		*failure_errno = EINVAL;
 		return -1;
 	}
+
+	if (trailing)
+		*trailing = p;
+
 	return 0;
 }
 
@@ -3439,6 +3443,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
 	struct fsck_ref_report report = {0};
+	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
 	struct object_id oid;
@@ -3458,13 +3463,29 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 
 	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
 				     ref_content.buf, &oid, &referent,
-				     &type, &failure_errno)) {
+				     &type, &trailing, &failure_errno)) {
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_CONTENT,
 				      "invalid ref content");
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_REF_MISSING_NEWLINE,
+					      "missing newline");
+			goto cleanup;
+		}
+
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing garbage in ref");
+			goto cleanup;
+		}
+	}
+
 cleanup:
 	strbuf_release(&refname);
 	strbuf_release(&ref_content);
refs/refs-internal.h +1 -1
index 2313c830d8..73b05f971b 100644
@@ -715,7 +715,7 @@ struct ref_store {
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno);
+			     const char **trailing, int *failure_errno);
 
 /*
  * Fill in the generic part of refs and add it to our collection of
t/t0602-reffiles-fsck.sh +60 -0

78 lines not on this page · raw message

Signature · 1 line
2.46.0
#86 lore raw

Re: [PATCH v4 3/5] ref: add more strict checks for regular refs

Review

Quoted 3 lines · shejialuo

+refMissingNewline::

(quoted diff) +2 -0
+	(INFO) A ref does not end with newline. This will be
+	considered an error in the future.

It is ONLY files backend's loose-ref representation to store the object name that is the value of the ref as hexadecimal text terminated with a newline. With packed backend, even if the file ends with an incomplete line, it would be confusing to say that such lack of terminating LF is associated with a particular ref. With reftable backend, the object name may not even be hexadecimal but binary without any terminating LF.

At least you should say "A loose ref file that does not end with...", because a ref NEVER ends or contains newline, and what you are expecting to be terminated with LF is not even a ref, but the value of it.

Also, isn't it too strong to say "will be" without giving any further information, like:

As valid implementations of Git never created such a loose ref file, it may become an error in the future. Report to the [email protected] mailing list if you see this error, as we need to know what tools created such a file.

or something?

The same comment applies to the next entry.

refs/files-backend.c int parse_loose_ref_contents(const struct git_hash_algo *algop, · line 626

Quoted 9 lines
refs/files-backend.c +4 -0
@@ -619,6 +619,10 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
 		*failure_errno = EINVAL;
 		return -1;
 	}
+
+	if (trailing)
+		*trailing = p;
+
 	return 0;

In the pre-context of this hunk, if parse_oid_hex_algoph() failed to recognise the initial segment of buf as a valid hexadecimal object name, it would have already returned, so we know 'p' is always valid here. It is the byte that comes immediately after the hexadecimal object name.

OK.

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3473

Quoted 11 lines
refs/files-backend.c +2 -1
 	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
 				     ref_content.buf, &oid, &referent,
-				     &type, &failure_errno)) {
+				     &type, &trailing, &failure_errno)) {
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_CONTENT,
 				      "invalid ref content");
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {

Just like we punted for S_ISLNK() in an earlier step, we for now deal with regular refs in this step. OK.

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3486

Quoted 13 lines
refs/files-backend.c +13 -0
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_REF_MISSING_NEWLINE,
+					      "missing newline");
+			goto cleanup;
+		}
+
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing garbage in ref");
+			goto cleanup;
+		}

Not limited to this patch, but isn't fsck_report_ref() misdesigned, or is it just they are used poorly in these patches? In these two callsites, the message string parameter does not give any more information than what the FSCK_MSG_* enum gives.

In fact, MSG_REF_MISSING_NEWLINE at least says that the complaint is about refs, but "missing newline" does not even say from what the newline is missing. For TRAILING_REF_CONTENT, people may expect to see what garbage follows the expected contents, but that information (i.e. contents of *trailing) is lost here.

author
#87 lore raw

Re: [PATCH v4 3/5] ref: add more strict checks for regular refs

Quoted 14 lines · Junio C Hamano

Review

Quoted 3 lines · shejialuo

+refMissingNewline::

(quoted diff) +2 -0
+	(INFO) A ref does not end with newline. This will be
+	considered an error in the future.

It is ONLY files backend's loose-ref representation to store the object name that is the value of the ref as hexadecimal text terminated with a newline. With packed backend, even if the file ends with an incomplete line, it would be confusing to say that such lack of terminating LF is associated with a particular ref. With reftable backend, the object name may not even be hexadecimal but binary without any terminating LF.

Quoted 5 lines

At least you should say "A loose ref file that does not end with...", because a ref NEVER ends or contains newline, and what you are expecting to be terminated with LF is not even a ref, but the value of it.

Thanks, I will improve this in the next version.

Quoted 10 lines

Also, isn't it too strong to say "will be" without giving any further information, like:

As valid implementations of Git never created such a loose ref file, it may become an error in the future. Report to the [email protected] mailing list if you see this error, as we need to know what tools created such a file.

or something?

This is nice. I know the intention here.

Quoted 19 lines

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3486

Quoted 13 lines
refs/files-backend.c +13 -0
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_REF_MISSING_NEWLINE,
+					      "missing newline");
+			goto cleanup;
+		}
+
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_TRAILING_REF_CONTENT,
+					      "trailing garbage in ref");
+			goto cleanup;
+		}

Not limited to this patch, but isn't fsck_report_ref() misdesigned, or is it just they are used poorly in these patches? In these two callsites, the message string parameter does not give any more information than what the FSCK_MSG_* enum gives.

Quoted 5 lines

In fact, MSG_REF_MISSING_NEWLINE at least says that the complaint is about refs, but "missing newline" does not even say from what the newline is missing. For TRAILING_REF_CONTENT, people may expect to see what garbage follows the expected contents, but that information (i.e. contents of *trailing) is lost here.

I agree with you here, I use way too general words to describe what happens. I will improve this. Actually, I feel hard to find words for "MSG_REF_MISSING_NEWLINE". I think we should say:

LF should be at the end of the file.

Thanks,
Jialuo

#88 lore raw

Re: [PATCH v4 3/5] ref: add more strict checks for regular refs

Quoted 5 lines · shejialuo

I agree with you here, I use way too general words to describe what happens. I will improve this. Actually, I feel hard to find words for "MSG_REF_MISSING_NEWLINE". I think we should say:

LF should be at the end of the file.

Giving a human-readable message when we have an enum can be done at a lot higher layer with the current way the fsck_report_ref() function is used (i.e. in that function, not by its callers).

That is what I meant by "misdesigned"---if one message enum always corresponds to one human-readable message, there is not much point in forcing callers to supply both, is there?

author
#89 lore raw

PATCH v4 4/5 ref: add symref content check for files backend

In reply to shejialuo

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of the symref itself.

A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

Thus, we could reuse "refMissingNewline" and "trailingRefContent" FSCK_INFOs to do the same retroactive tightening as we introduce for regular references.

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

And we introduce a new "badReferentName(ERROR)" fsck message to report above errors to the user.

In order to check the content of the symref, create a function "files_fsck_symref_target". It will first check whether the "referent" is under the "refs/" directory, if not, we will report "escapeReferent" fsck error message to notify the user this situation.

Then, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'.

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to check whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

At last, we need to check whether the referent is a directory. We cannot distinguish whether a given reference like "refs/heads/a" is a file or a directory by using "check_refname_format". We have already checked bad file type when iterating the "refs/" directory but we ignore the directory. Thus, we need to explicitly add check here.

---
 Documentation/fsck-msgids.txt |   9 +++
 fsck.h                        |   3 +
 refs/files-backend.c          |  81 +++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 117 ++++++++++++++++++++++++++++++++++
 4 files changed, 210 insertions(+)
Documentation/fsck-msgids.txt +9 -0
index 8827137ef0..03bcb77972 100644
@@ -28,6 +28,12 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badReferentFiletype`::
+	(ERROR) The referent of a symref has a bad file type.
+
+`badReferentName`::
+	(ERROR) The referent name of a symref is invalid.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
@@ -49,6 +55,9 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
+`escapeReferent`::
+	(ERROR) The referent of a symref is outside the "ref" directory.
+
 `extraHeaderEntry`::
 	(IGNORE) Extra headers found after `tagger`.
 
fsck.h +3 -0
index b85072df57..c90561c6db 100644
@@ -34,11 +34,14 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_REFERENT_FILETYPE, ERROR) \
+	FUNC(BAD_REFERENT_NAME, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
 	FUNC(BAD_TYPE, ERROR) \
 	FUNC(DUPLICATE_ENTRIES, ERROR) \
+	FUNC(ESCAPE_REFERENT, ERROR) \
 	FUNC(MISSING_AUTHOR, ERROR) \
 	FUNC(MISSING_COMMITTER, ERROR) \
 	FUNC(MISSING_EMAIL, ERROR) \
refs/files-backend.c +81 -0

104 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +117 -0

121 lines not on this page · raw message

Signature · 1 line
2.46.0
#90 lore raw

Re: [PATCH v4 4/5] ref: add symref content check for files backend

shejialuo <[email protected]> writes:

Expect that people do not read the body of the message as completing a paragrpah the title started. I.e. ...

Quoted 3 lines

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of the symref itself.

... this needs a bit of preamble, like

We have code that check regular ref contents, but we do not yet check contents of symbolic refs.

Quoted 12 lines

A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

Quoted 6 lines

Thus, we could reuse "refMissingNewline" and "trailingRefContent" FSCK_INFOs to do the same retroactive tightening as we introduce for regular references.

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

This description needs to be updated, as it is unclear if you are talking about errors we already detect, or if you are planning to update fsck to notice and report these errors.

Quoted 5 lines

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

And we introduce a new "badReferentName(ERROR)" fsck message to report above errors to the user.

OK.

Quoted 7 lines

In order to check the content of the symref, create a function "files_fsck_symref_target". It will first check whether the "referent" is under the "refs/" directory, if not, we will report "escapeReferent" fsck error message to notify the user this situation.

Then, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'.

"Then, we will first" -> "Then it checks" or something.

You already consumed "first" for the check to limit the referent to those under "refs/" hierarchy.

Quoted 7 lines

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to check whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

That is an implementation detail of what you did. But if the implementation were buggy and did not exactly what you intended to do, the above description gives no information to help others to fix it up so that it works as you intended it to work, because you do not explain it.

So what did you want to achieve in the third step (the first being "limit to refs/ hiararchy", the second being "no incomplete lines allowed")?

Third, we want to make sure that the contents of a textual symref MUST have a single LF after the target refname and NOTHING ELSE.

or something.

Quoted 1 line

At last, we need to check whether the referent is a directory. We cannot

"a directory" -> "an existing directory"?

I am not comfortable to see the word "directory" used in this proposed log message, as some refs could be stored in the packed backend and are referenced by the symbolic ref you are inspecting (this comment also refers to the "refs/ directory" you mentioned earlier as "the first check").

Lastly, a symbolic ref MUST either point to an existing ref, or if the referent does not exist, it MUST NOT be a leading subpath for another existing ref (e.g., when "refs/heads/main" exists, a symbolic ref that points at "refs/heads" is a no-no).

or something (but again, I am open to a phrasing better than "subpath").

Design question. What do we want to do when we have no loose refs under the "refs/heads/historical/" hiearchy, (i.e. all of them are in packed-refs file) hence ".git/refs/heads/historical" directory does not exist on the filesystem. And a symbolic ref points at "refs/heads/historical". Shouldn't we give the same error whether the .git/refs/heads/historical directory exist or not, as long as the refs/heads/historical/main branch exists (in the packed-refs backend)?

Quoted 18 lines
Documentation/fsck-msgids.txt +6 -0
index 8827137ef0..03bcb77972 100644
@@ -28,6 +28,12 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badReferentFiletype`::
+	(ERROR) The referent of a symref has a bad file type.
+
+`badReferentName`::
+	(ERROR) The referent name of a symref is invalid.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 

Documentation/fsck-msgids.txt line 59

Quoted 5 lines
Documentation/fsck-msgids.txt +2 -0
@@ -49,6 +55,9 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
+`escapeReferent`::
+	(ERROR) The referent of a symref is outside the "ref" directory.

I am not sure starting this as ERROR is wise. Users and third-party tools make creative uses of the system and I cannot offhand think of an argument why it should be forbidden to create a symbolic link to our own HEAD or to some worktree-specific ref in another worktree.

Documentation/fsck-msgids.txt

Quoted 12 lines
Documentation/fsck-msgids.txt +12 -0
+	size_t len = referent->len - 1;
+	struct stat st;
+	int ret = 0;
+
+	if (!starts_with(referent->buf, "refs/")) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_ESCAPE_REFERENT,
+				      "points to ref outside the refs directory");
+		goto out;
+	}
+
+	if (referent->buf[referent->len - 1] != '\n') {

As you initialized "len" to "referent->len-1" earlier, wouldn't it more natural to use it here? That would match the incrementing of len++ later in this block.

Documentation/fsck-msgids.txt

Quoted 5 lines
Documentation/fsck-msgids.txt +5 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+		len++;
+	}

Having said that, the above should be simplified more like:

* declare but not initialize "len". better yet, declare "orig_len" and leave it uninitialized.

* do not touch "len++" in the above block (actually, you can discard the above "if(it does not end with LF)" block, see below).

* instead grab "referent->len" in "len" (or "orig_len") immediately before you first modify referent, i.e. before strbuf_rtrim() call.

	orig_len = referent->len;
	orig_last_byte = referent->buf[orig_len - 1];

Documentation/fsck-msgids.txt

Quoted 5 lines
Documentation/fsck-msgids.txt +5 -0
+	strbuf_rtrim(referent);
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_REFERENT_NAME,
+				      "points to refname with invalid format");

Similar to an earlier step, the message does not give any more information than the enum. Wouldn't the user who got this error want to learn what referent->buf said and which part of it was bad in the same message, instead of having to look it up on their own after fsck finishes?

Documentation/fsck-msgids.txt

Quoted 2 lines
Documentation/fsck-msgids.txt +2 -0
+		goto out;
+	}

At this point we know check_refname_format() is happy with what is left after rtrimming the referent. There are four cases:

- rtrim() did not trim anything (orig_len == referent->len); the file lacked the terminating LF.

- rtrim() trimmed one byte (orig_len - 1 == referent->len) and the byte was not LF (orig_last_byte != '\n'). The file lacked the terminating LF.

- rtrim() trimmed exactly one byte (orig_len - 1 == referent->len) and the byte was LF (orig_last_byte == '\n'). There is no error.

- all other cases, i.e., rtrim() trimmed two or more bytes. The file had trailing whitespaces after a valid referent that passed check_refname_format().

So in short,

	if (referent->len == orig_len ||
	    referent->len == orig_len - 1 && orig_last_byte != '\n') {
		FSCK_MSG_REF_MISSING_NEWLINE;
	} else if (referent->len < orig_len - 1) {
		FSCK_MSG_REF_TRAILING_WHITESPACE;
	}

can replace the next block you wrote, and we can also remove the earlier "it is an error if it does not end with '\n'", I think.

Documentation/fsck-msgids.txt

Quoted 4 lines
Documentation/fsck-msgids.txt +4 -0
+	if (len != referent->len) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_TRAILING_REF_CONTENT,
+				      "trailing garbage in ref");

As check_refname_format() was happy, the difference between orig_len and referent->len are only coming from trailing whitespaces, i.e. it is not that it had arbitrary garbage. Shouldn't we be more explicit about that?

Quoted 12 lines
Documentation/fsck-msgids.txt +12 -0
+	/*
+	 * Dangling symrefs are common and so we don't report them.
+	 */
+	if (lstat(referent_path->buf, &st)) {
+		if (errno != ENOENT) {
+			ret = error_errno(_("unable to stat '%s'"),
+					  referent_path->buf);
+		}
+		goto out;
+	}
+
+	/*

Documentation/fsck-msgids.txt

Quoted 10 lines
Documentation/fsck-msgids.txt +10 -0
+	 * We cannot distinguish whether "refs/heads/a" is a directory or not by
+	 * using "check_refname_format(referent->buf, 0)". Instead, we need to
+	 * check the file type of the target.
+	 */
+	if (S_ISDIR(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_REFERENT_FILETYPE,
+				      "points to the directory");
+		goto out;
+	}

If referent_path->buf refers to "refs/heads/historical/", and all the branches under the hierarchy have been sent to packed-refs, then this check will not trigger.

I wonder if this check is the right thing to enforce in the first place, though.

As far as the end user is concerned, refs/heads/historical/master branch stil exists, and there is no refs/heads/historical branch, so such a symbolic ref, for all intents and purposes, is the same as any other dangling symbolic refs, no?

Of course, "git update-ref SUCH_A_SYMREF HEAD" will complain because there is refs/heads/historical, with something like

"refs/heads/historical/master" exists, cannot create "refs/heads/historical"

but that is to be expected. If you remove the last branch in the refs/heads/historical hierarchy, you should be able to do such an update-ref to instanciate refs/heads/historical as a regular ref.

Documentation/fsck-msgids.txt static int files_fsck_refs_content(struct ref_store *ref_store, · line 3563

Quoted 12 lines
Documentation/fsck-msgids.txt +8 -0
@@ -3484,12 +3553,24 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 					      "trailing garbage in ref");
 			goto cleanup;
 		}
+	} else {
+		strbuf_addf(&referent_path, "%s/%s",
+			    ref_store->gitdir, referent.buf);
+		/*
+		 * the referent may contain the spaces and the newline, need to
+		 * trim for path.
+		 */
+		strbuf_rtrim(&referent_path);

I doubt this is a good design. We have referent, and the symbolic ref checker knows that the true referent refname may be followed by whitespaces, so instead of inventing referent _path here, it would be a better design to let the files_fsck_symref_target() to decide what file to open and check based on referent, no? Give it the refstore or refstore's gitdir and have the concatenation with the rtrimmed contents in the referent->buf after it inspected it instead, perhaps?

Quoted 3 lines
Documentation/fsck-msgids.txt +3 -0
+		ret = files_fsck_symref_target(o, &report,
+					       &referent,
+					       &referent_path);
author
#91 lore raw

Re: [PATCH v4 4/5] ref: add symref content check for files backend

Quoted 5 lines · Junio C Hamano

shejialuo <[email protected]> writes:

Expect that people do not read the body of the message as completing a paragrpah the title started. I.e. ...

Quoted 9 lines
Quoted 3 lines

We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of the symref itself.

... this needs a bit of preamble, like

We have code that check regular ref contents, but we do not yet check contents of symbolic refs.

Thanks, I will improve this in the next version.

Quoted 16 lines
Quoted 16 lines

A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref:

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

Thus, we could reuse "refMissingNewline" and "trailingRefContent" FSCK_INFOs to do the same retroactive tightening as we introduce for regular references.

Quoted 7 lines
Quoted 2 lines

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

This description needs to be updated, as it is unclear if you are talking about errors we already detect, or if you are planning to update fsck to notice and report these errors.

Yes, When I was writing this part, I felt a little painful to express my words. I have thought how could I express the connection between the current patch and the previous one.

Quoted 18 lines
Quoted 7 lines

And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to check whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content.

That is an implementation detail of what you did. But if the implementation were buggy and did not exactly what you intended to do, the above description gives no information to help others to fix it up so that it works as you intended it to work, because you do not explain it.

So what did you want to achieve in the third step (the first being "limit to refs/ hiararchy", the second being "no incomplete lines allowed")?

Quoted 6 lines

Third, we want to make sure that the contents of a textual symref MUST have a single LF after the target refname and NOTHING ELSE.

or something.

From the above comments, I need to organize the commit message of this patch to make things clear here.

Quoted 16 lines

"a directory" -> "an existing directory"?

I am not comfortable to see the word "directory" used in this proposed log message, as some refs could be stored in the packed backend and are referenced by the symbolic ref you are inspecting (this comment also refers to the "refs/ directory" you mentioned earlier as "the first check").

Lastly, a symbolic ref MUST either point to an existing ref, or if the referent does not exist, it MUST NOT be a leading subpath for another existing ref (e.g., when "refs/heads/main" exists, a symbolic ref that points at "refs/heads" is a no-no).

or something (but again, I am open to a phrasing better than "subpath").

Quoted 9 lines

Design question. What do we want to do when we have no loose refs under the "refs/heads/historical/" hiearchy, (i.e. all of them are in packed-refs file) hence ".git/refs/heads/historical" directory does not exist on the filesystem. And a symbolic ref points at "refs/heads/historical". Shouldn't we give the same error whether the .git/refs/heads/historical directory exist or not, as long as the refs/heads/historical/main branch exists (in the packed-refs backend)?

I guess I need to think carefully here. Actually, my intention is that I want to concentrate on the loose refs and then take consideration about the packed refs.

However, from what you have said above, it seems I could not do this. They are connected. But at current, I am not so familiar with packed refs behavior, I could not answer all the questions above.

I decide to understand what packed-ref done. So, this series may be stalled sometime until I have a good knowledge and re-think the design here.

Quoted 8 lines

Documentation/fsck-msgids.txt line 59

Quoted 2 lines

+escapeReferent::

Documentation/fsck-msgids.txt +1 -0
+	(ERROR) The referent of a symref is outside the "ref" directory.

I am not sure starting this as ERROR is wise. Users and third-party tools make creative uses of the system and I cannot offhand think of an argument why it should be forbidden to create a symbolic link to our own HEAD or to some worktree-specific ref in another worktree.

Do we allow this cross-access (hack)? It might cause some trouble from my perspective.

Quoted 6 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3457

Quoted 1 line
refs/files-backend.c +1 -0
+	if (referent->buf[referent->len - 1] != '\n') {

As you initialized "len" to "referent->len-1" earlier, wouldn't it more natural to use it here? That would match the incrementing of len++ later in this block.

Yes, exactly.

Quoted 15 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3462

Quoted 5 lines
refs/files-backend.c +5 -0
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_REF_MISSING_NEWLINE,
+				      "missing newline");
+		len++;
+	}

Having said that, the above should be simplified more like:

* declare but not initialize "len". better yet, declare "orig_len" and leave it uninitialized.

* do not touch "len++" in the above block (actually, you can discard the above "if(it does not end with LF)" block, see below).

Quoted 6 lines

* instead grab "referent->len" in "len" (or "orig_len") immediately before you first modify referent, i.e. before strbuf_rtrim() call.

	orig_len = referent->len;
	orig_last_byte = referent->buf[orig_len - 1];

I agree.

Quoted 12 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3468

Quoted 5 lines
refs/files-backend.c +5 -0
+	strbuf_rtrim(referent);
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_REFERENT_NAME,
+				      "points to refname with invalid format");

Similar to an earlier step, the message does not give any more information than the enum. Wouldn't the user who got this error want to learn what referent->buf said and which part of it was bad in the same message, instead of having to look it up on their own after fsck finishes?

Yes, I agree. I will improve this.

Quoted 13 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+		goto out;
+	}

At this point we know check_refname_format() is happy with what is left after rtrimming the referent. There are four cases:

- rtrim() did not trim anything (orig_len == referent->len); the file lacked the terminating LF.

- rtrim() trimmed one byte (orig_len - 1 == referent->len) and the byte was not LF (orig_last_byte != '\n'). The file lacked the terminating LF.

Quoted 7 lines

- rtrim() trimmed exactly one byte (orig_len - 1 == referent->len) and the byte was LF (orig_last_byte == '\n'). There is no error.

- all other cases, i.e., rtrim() trimmed two or more bytes. The file had trailing whitespaces after a valid referent that passed check_refname_format().

That's so clear. My implementation is not good compared with this.

Quoted 12 lines

So in short,

	if (referent->len == orig_len ||
	    referent->len == orig_len - 1 && orig_last_byte != '\n') {
		FSCK_MSG_REF_MISSING_NEWLINE;
	} else if (referent->len < orig_len - 1) {
		FSCK_MSG_REF_TRAILING_WHITESPACE;
	}

can replace the next block you wrote, and we can also remove the earlier "it is an error if it does not end with '\n'", I think.

Quoted 10 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3475

Quoted 4 lines
refs/files-backend.c +4 -0
+	if (len != referent->len) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_TRAILING_REF_CONTENT,
+				      "trailing garbage in ref");

As check_refname_format() was happy, the difference between orig_len and referent->len are only coming from trailing whitespaces, i.e. it is not that it had arbitrary garbage. Shouldn't we be more explicit about that?

Yes, I made a lot of mistakes when calling the "fsck_report_ref". I will report the exact garbage content to the user.

Quoted 18 lines
Quoted 18 lines
refs/files-backend.c +18 -0
+	/*
+	 * Dangling symrefs are common and so we don't report them.
+	 */
+	if (lstat(referent_path->buf, &st)) {
+		if (errno != ENOENT) {
+			ret = error_errno(_("unable to stat '%s'"),
+					  referent_path->buf);
+		}
+		goto out;
+	}
+
+	/*
+	 * We cannot distinguish whether "refs/heads/a" is a directory or not by
+	 * using "check_refname_format(referent->buf, 0)". Instead, we need to
+	 * check the file type of the target.
+	 */
+	if (S_ISDIR(st.st_mode)) {
+		ret = fsck_report_ref(o, report,
Quoted 9 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3499

Quoted 4 lines
refs/files-backend.c +4 -0
+				      FSCK_MSG_BAD_REFERENT_FILETYPE,
+				      "points to the directory");
+		goto out;
+	}

If referent_path->buf refers to "refs/heads/historical/", and all the branches under the hierarchy have been sent to packed-refs, then this check will not trigger.

Yes, because "refs/heads/historical" will not appear in the filesystem.

Quoted 8 lines

I wonder if this check is the right thing to enforce in the first place, though.

As far as the end user is concerned, refs/heads/historical/master branch stil exists, and there is no refs/heads/historical branch, so such a symbolic ref, for all intents and purposes, is the same as any other dangling symbolic refs, no?

Quoted 9 lines

Of course, "git update-ref SUCH_A_SYMREF HEAD" will complain because there is refs/heads/historical, with something like

"refs/heads/historical/master" exists, cannot create "refs/heads/historical"

but that is to be expected. If you remove the last branch in the refs/heads/historical hierarchy, you should be able to do such an update-ref to instanciate refs/heads/historical as a regular ref.

I am a little shocked here. I do this in action and find the directory will be automatically converted to a regular file in the filesystem. So, I agree with you here. We should never check this, because we allow symref to point to a directory. As long as there is no loose refs and packed refs under this directory, we could use "git update-ref" for this symref.

Thanks,

Quoted 13 lines

refs/files-backend.c static int files_fsck_refs_content(struct ref_store *ref_store, · line 3563

Quoted 12 lines
refs/files-backend.c +8 -0
@@ -3484,12 +3553,24 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 					      "trailing garbage in ref");
 			goto cleanup;
 		}
+	} else {
+		strbuf_addf(&referent_path, "%s/%s",
+			    ref_store->gitdir, referent.buf);
+		/*
+		 * the referent may contain the spaces and the newline, need to
+		 * trim for path.
+		 */
+		strbuf_rtrim(&referent_path);
Quoted 9 lines

I doubt this is a good design. We have referent, and the symbolic ref checker knows that the true referent refname may be followed by whitespaces, so instead of inventing referent _path here, it would be a better design to let the files_fsck_symref_target() to decide what file to open and check based on referent, no? Give it the refstore or refstore's gitdir and have the concatenation with the rtrimmed contents in the referent->buf after it inspected it instead, perhaps?

Yes, I agree with you here. We should use "files_fsck_symref_target" to do this.

----

From this review, I think I need to understand more behaviors about files backend and packed backend. Thanks for your so dedicated reviews. I may spend more time to send the next version. And there may be some delay.

Thanks,
Jialuo

#92 lore raw

Re: [PATCH v4 4/5] ref: add symref content check for files backend

Quoted 10 lines · shejialuo
Quoted 8 lines

Documentation/fsck-msgids.txt line 59

Quoted 2 lines

+escapeReferent::

Documentation/fsck-msgids.txt +1 -0
+	(ERROR) The referent of a symref is outside the "ref" directory.

I am not sure starting this as ERROR is wise. Users and third-party tools make creative uses of the system and I cannot offhand think of an argument why it should be forbidden to create a symbolic link to our own HEAD or to some worktree-specific ref in another worktree.

Do we allow this cross-access (hack)? It might cause some trouble from my perspective.

If the current implementation allows users to set up and take advantage of, then it is not a hack. It would cause breakage if we make it an error. Does such a symref successfully refer to the referent right now? I think it does.

Thanks.

author
#93 lore raw

PATCH v4 5/5 ref: add symlink ref content check for files backend

In reply to shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

We firstly use the "strbuf_add_real_path" to resolve the symlink and get the absolute path "referent_path" which the symlink ref points to. Then we can get the absolute path "abs_gitdir" of the "gitdir". By combining "referent_path" and "abs_gitdir", we can extract the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

Because we consider deprecating writing the symbolic links and for reading, we may or may not deprecate. We first need to asses whether symbolic links may still be used. So, add a new fsck message "symlinkRef(INFO)" to let the user be aware of this information.

---
 Documentation/fsck-msgids.txt |  5 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 65 ++++++++++++++++++-----
 t/t0602-reffiles-fsck.sh      | 97 +++++++++++++++++++++++++++++++++++
 4 files changed, 154 insertions(+), 14 deletions(-)
Documentation/fsck-msgids.txt +5 -0
index 03bcb77972..31626e765b 100644
@@ -186,6 +186,11 @@
 	(INFO) A ref does not end with newline. This will be
 	considered an error in the future.
 
+`symlinkRef`::
+	(INFO) A symref uses the symbolic link. This kind of symref may
+	be considered ERROR in the future when totally dropping the
+	symlink support.
+
 `trailingRefContent`::
 	(INFO) A ref has trailing content. This will be
 	considered an error in the future.
fsck.h +1 -0
index c90561c6db..b72ee632a4 100644
@@ -89,6 +89,7 @@ enum fsck_msg_type {
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
 	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(SYMLINK_REF, INFO) \
 	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
refs/files-backend.c +51 -14

146 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +97 -0

101 lines not on this page · raw message

Signature · 1 line
2.46.0
#94 lore raw

Re: [PATCH v4 5/5] ref: add symlink ref content check for files backend

Quoted 4 lines · shejialuo

Because we consider deprecating writing the symbolic links and for reading, we may or may not deprecate. We first need to asses whether symbolic links may still be used. So, add a new fsck message "symlinkRef(INFO)" to let the user be aware of this information.

If that is the intention, the the documentation entry is somewhat out of line.

Documentation/fsck-msgids.txt line 192

Quoted 4 lines

+symlinkRef::

Documentation/fsck-msgids.txt +3 -0
+	(INFO) A symref uses the symbolic link. This kind of symref may
+	be considered ERROR in the future when totally dropping the
+	symlink support.

A symbolic link is used as a symref. Report to the [email protected] mailing list if you see this error, as we are assessing the feasibility of dropping the support to use symbolic links as a symref.

But quite honestly, I do not think it is necessary to deprecate (let alone remove) the support for reading side.

#95 lore raw

Re: [PATCH v4 0/5] add ref content check for files backend

In reply to shejialuo
Quoted 2 lines · shejialuo

Because I add more commits, I provide the "--interdiff" here to make the reviewer's life easier.

Yeah, for the changes from the previous iteration of this series, range-diff comparison is pretty much useless. Interdiff is indeed more usable, but essentially this iteration deserves reviews with fresh sets of eyes.

Quoted 2 lines

However, because I have not merged the latest ci fixup, so I cannot verify some jobs in CIs. May need the help from Junio to verify.

A good way to do so is to fork a temporary branch at the tip of these 5 commits, and then either merge or cherry-pick the CI fixup. Such a temporary branch should be usable for CI testing, right?

Thanks.

PS.

I am not feeling well today; please expect delayed and/or sparse responses.

author
#96 lore raw

v5 cover add ref content check for files backend

In reply to shejialuo

Hi All:

This version handles a lot of review from Junio.

1. [PATCH v5 1/9] enhances the commit message compared with the previous [PATCH v4 1/5].

2. [PATCH v5 2/9] is a new topic which has not never been introduced in the previous. It supports multiple worktrees check for refs. During the GSoC PATCH: <[email protected]>, I do not implement the code to support worktree check. However, we need to add this due to the review from Junio:

Quoted 7 lines

Documentation/fsck-msgids.txt line 59

Quoted 2 lines

+escapeReferent::

Documentation/fsck-msgids.txt +1 -0
+	(ERROR) The referent of a symref is outside the "ref" directory.

I am not sure starting this as ERROR is wise. Users and third-party tools make creative uses of the system and I cannot offhand think of an argument why it should be forbidden to create a symbolic link to our own HEAD or to some worktree-specific ref in another worktree.

When checking the escape situation of the referent, I didn't consider the worktree. So, I decide to first add checks for multiple worktree. And then add a new test for multiple worktrees.

3. The intention of the [PATCH v5 3/9] is the same as the [PATCH v4 2/5].

+ Enhance the commit message suggested by Junio.
+ Use "fsck_ref_report" to tell the user we cannot read the file instead of reporting general error.
+ For "FSCK_MSG_BAD_REF_CONTENT" message id, instead of just reporting the no-information message "invalid ref content", report the actual content of the ref, i.e., "ref_content.buf".

4. The intention of the [PATCH v5 4/9] is the same as the [PATCH v4 3/5].

+ Instead of using the concrete "refMissingNewline" and "trailingRefContent" fsck messages, create a fsck info message "unofficialFormattedRef"
+ Follow the advice from Junio, use "fsck_ref_report" to report more useful information. For example, what is the trailing garbage.

5. The PATCH[v4 4/5] is split into 4 commits from [PATCH v5 5/9] to [PATCH v5 8/9]. The reason why I decide to do this is that I introduce the check for worktree and the version 4 is a little messy for the commit message. Although the C code is not changed too much, the commit message is hard to write and make the reviewer confused.

6. [PATCH v5 5/9] aims to add checks for textual symref except escape situation.

+ Because I split commit here, it's easy to write the clean commit message, which should be changed according to the review from Junio.
+ Followed the advice from Junio to gracefully check the symref. Thus, the commit message is more clean.
+ Drop the check for "referent" pointing to a directory. We allow this, it's a dangling symref. No need to check this. So we could drop the parameter "referent_path" in "files_fsck_symref_target()".
+ Enhance the "fsck_ref_report" to report more useful information.

7. [PATCH v5 6/9] enhances the check for escape situation. Introduce a new fsck message "escapeReferent(INFO)".

8. [PATCH v5 7/9] enhances the situation where we use multiple worktrees. In practice, we allow point to ref of one of the linked worktrees from primary worktree or one of the linked worktrees. We should not warn about this.

9. [PATCH v5 8/9] enhances the test script for worktrees.

10. The intention of [PATCH v5 9/9] is the same as the [PATCH v4 5/5]. Not so much change.

Because I do not sync the upstream for a long time. For this series, I sync the latest upstream and generate the patch, it is based on

3857aae53f (Git 2.47-rc0, 2024-09-25)

And I don't think range-diff is useful, it is messy for the reviewers. Actually, there are not so many logic changes in this new version.

Thanks,
Jialuo

shejialuo (9):
ref: initialize "fsck_ref_report" with zero builtin/refs: support multiple worktrees check for refs.
ref: port git-fsck(1) regular refs check for files backend
ref: add more strict checks for regular refs
ref: add basic symref content check for files backend
ref: add escape check for the referent of symref
ref: enhance escape situation for worktrees
t0602: add ref content checks for worktrees
ref: add symlink ref content check for files backend

 Documentation/fsck-msgids.txt |  28 +++
 builtin/refs.c                |  11 +-
 fsck.h                        |   5 +
 refs.c                        |   2 +-
 refs/files-backend.c          | 168 ++++++++++++-
 refs/refs-internal.h          |   2 +-
 t/t0602-reffiles-fsck.sh      | 442 ++++++++++++++++++++++++++++++++++
 7 files changed, 646 insertions(+), 12 deletions(-)
Signature · 1 line
2.46.2
author
#97 lore raw

PATCH v5 1/9 ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use "{ 0 }" to express that we are 0-initializing everything. In order to align with the the codebase, initialize "fsck_ref_report" with zero.

---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 0824c0b8a9..03d2503276 100644
@@ -3520,7 +3520,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = { 0 };
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.46.2
#98 lore raw

Re: [PATCH v5 1/9] ref: initialize "fsck_ref_report" with zero

Quoted 9 lines · shejialuo

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use "{ 0 }" to express that we are 0-initializing everything. In order to align with the the

s/the//

[snip]

author
#99 lore raw

PATCH v5 2/9 builtin/refs: support multiple worktrees check for refs.

In reply to shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees. Use "get_worktrees" and "get_worktree_ref_store" to check refs under the worktrees.

Because we should only check once for "packed-refs", let's call the fsck function for packed-backend when in the main worktree. In order to know which directory we check, we should default print this information instead of specifying "--verbose".

It's not suitable to print these information to the stderr. So, change to stdout.

---
 builtin/refs.c           | 11 ++++++--
 refs/files-backend.c     | 18 ++++++++----
 t/t0602-reffiles-fsck.sh | 59 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+), 7 deletions(-)
builtin/refs.c +9 -2
index 24978a7b7b..3c492ea922 100644
@@ -5,6 +5,7 @@
 #include "parse-options.h"
 #include "refs.h"
 #include "strbuf.h"
+#include "worktree.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--dry-run]")
@@ -66,6 +67,7 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
 static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 {
 	struct fsck_options fsck_refs_options = FSCK_REFS_OPTIONS_DEFAULT;
+	struct worktree **worktrees, **p;
 	const char * const verify_usage[] = {
 		REFS_VERIFY_USAGE,
 		NULL,
@@ -75,7 +77,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "strict", &fsck_refs_options.strict, N_("enable strict checking")),
 		OPT_END(),
 	};
-	int ret;
+	int ret = 0;
 
 	argc = parse_options(argc, argv, prefix, options, verify_usage, 0);
 	if (argc)
@@ -84,9 +86,14 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	git_config(git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(the_repository);
 
-	ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
+	worktrees = get_worktrees();
+	for (p = worktrees; *p; p++) {
+		struct worktree *wt = *p;
+		ret += refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options);
+	}
 
 	fsck_options_clear(&fsck_refs_options);
+	free_worktrees(worktrees);
 	return ret;
 }
 
refs/files-backend.c +13 -5
index 03d2503276..57318b4c4e 100644
@@ -3558,7 +3558,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
 			if (o->verbose)
-				fprintf_ln(stderr, "Checking %s/%s",
+				fprintf_ln(stdout, "Checking %s/%s",
 					   refs_check_dir, iter->relative_path);
 			for (size_t i = 0; fsck_refs_fn[i]; i++) {
 				if (fsck_refs_fn[i](ref_store, o, refs_check_dir, iter))
@@ -3589,8 +3589,8 @@ static int files_fsck_refs(struct ref_store *ref_store,
 		NULL,
 	};
 
-	if (o->verbose)
-		fprintf_ln(stderr, _("Checking references consistency"));
+	fprintf_ln(stdout, _("Checking references consistency in %s"),
+		   ref_store->gitdir);
 	return files_fsck_refs_dir(ref_store, o,  "refs", fsck_refs_fn);
 }
 
@@ -3600,8 +3600,16 @@ static int files_fsck(struct ref_store *ref_store,
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_READ, "fsck");
 
-	return files_fsck_refs(ref_store, o) |
-	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+	int ret = files_fsck_refs(ref_store, o);
+
+	/*
+	 * packed-refs should only be checked once because it is shared
+	 * between all worktrees.
+	 */
+	if (!strcmp(ref_store->gitdir, ref_store->repo->gitdir))
+		ret += refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+
+	return ret;
 }
 
 struct ref_storage_be refs_be_files = {
t/t0602-reffiles-fsck.sh +59 -0

63 lines not on this page · raw message

Signature · 1 line
2.46.2
#100 lore raw

Re: [PATCH v5 2/9] builtin/refs: support multiple worktrees check for refs.

Quoted 5 lines · shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees. Use "get_worktrees" and "get_worktree_ref_store" to check refs under the worktrees.

Makes sense.

Quoted 4 lines

Because we should only check once for "packed-refs", let's call the fsck function for packed-backend when in the main worktree. In order to know which directory we check, we should default print this information instead of specifying "--verbose".

This change should likely be evicted into its own commit with a bit more explanation.

Quoted 2 lines

It's not suitable to print these information to the stderr. So, change to stdout.

This one, too. Why exactly is in not suitable to print to stderr?

Quoted 39 lines
---
 builtin/refs.c           | 11 ++++++--
 refs/files-backend.c     | 18 ++++++++----
 t/t0602-reffiles-fsck.sh | 59 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+), 7 deletions(-)
builtin/refs.c +3 -1
index 24978a7b7b..3c492ea922 100644
@@ -5,6 +5,7 @@
 #include "parse-options.h"
 #include "refs.h"
 #include "strbuf.h"
+#include "worktree.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--dry-run]")
@@ -66,6 +67,7 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
 static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 {
 	struct fsck_options fsck_refs_options = FSCK_REFS_OPTIONS_DEFAULT;
+	struct worktree **worktrees, **p;
 	const char * const verify_usage[] = {
 		REFS_VERIFY_USAGE,
 		NULL,
@@ -75,7 +77,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "strict", &fsck_refs_options.strict, N_("enable strict checking")),
 		OPT_END(),
 	};
-	int ret;
+	int ret = 0;
 
 	argc = parse_options(argc, argv, prefix, options, verify_usage, 0);
 	if (argc)

builtin/refs.c static int cmd_refs_verify(int argc, const char **argv, const char *prefix) · line 93

Quoted 9 lines
builtin/refs.c +5 -1
@@ -84,9 +86,14 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	git_config(git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(the_repository);
 
-	ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
+	worktrees = get_worktrees();
+	for (p = worktrees; *p; p++) {
+		struct worktree *wt = *p;
+		ret += refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options);
+	}

I think it is more customary to say ret |= instead of ref +=. Otherwise we could at least in theory wrap around and even land at ret == 0, even though this is quite unlikely.

Quoted 5 lines
builtin/refs.c +1 -0
 	fsck_options_clear(&fsck_refs_options);
+	free_worktrees(worktrees);
 	return ret;
 }
 

[snip]

Quoted 9 lines
builtin/refs.c +3 -2
@@ -3600,8 +3600,16 @@ static int files_fsck(struct ref_store *ref_store,
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_READ, "fsck");
 
-	return files_fsck_refs(ref_store, o) |
-	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+	int ret = files_fsck_refs(ref_store, o);
+
+	/*

builtin/refs.c static int files_fsck(struct ref_store *ref_store, · line 3615

Quoted 10 lines
builtin/refs.c +7 -0
+	 * packed-refs should only be checked once because it is shared
+	 * between all worktrees.
+	 */
+	if (!strcmp(ref_store->gitdir, ref_store->repo->gitdir))
+		ret += refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+
+	return ret;
 }
 
 struct ref_storage_be refs_be_files = {

What is the current behaviour? Is it that we verify the packed-refs file multiple times, or rather that we call packed_ref_store->be->fsck() many times even though we know it won't do anything for anything except for the main worktree?

If it is the former I very much agree that we should make this conditional. If it's the latter I'm more in the camp of letting it be such that if worktrees were to ever gain support for "packed-refs" we wouldn't have to change anything.

In any case, as proposed I think it would make sense to evict this into a standalone commit such that these details can be explained in the commit message.

Patrick

author
#101 lore raw

Re: [PATCH v5 2/9] builtin/refs: support multiple worktrees check for refs.

Quoted 14 lines · Patrick Steinhardt
Quoted 5 lines · shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees. Use "get_worktrees" and "get_worktree_ref_store" to check refs under the worktrees.

Makes sense.

Quoted 4 lines

Because we should only check once for "packed-refs", let's call the fsck function for packed-backend when in the main worktree. In order to know which directory we check, we should default print this information instead of specifying "--verbose".

Quoted 8 lines

This change should likely be evicted into its own commit with a bit more explanation.

Quoted 2 lines

It's not suitable to print these information to the stderr. So, change to stdout.

This one, too. Why exactly is in not suitable to print to stderr?

I am sorry for the confusion. We should not print which directory we check here into stderr. Because I think this will make test script contain many unrelated info when using "git refs verify 2>err".

The reason here is when checking the consistency of refs in multiple worktrees. The ref name could be repeat. For example, worktree A has its own ref called "test" under ".git/worktrees/A/refs/worktree/test" and worktree B has its own ref still called "test" under ".git/worktrees/B/refs/worktree/test".

However, the refname would be printed to "refs/worktree/test". It will make the user confused which "refs/worktree/test" is checked. So, we should print this information like:

Checking references consistency in .git
...
checking references consistency in .git/worktrees/A
...
checking references consistency in .git/worktrees/B

However, when writing this, I feel a ".git" is a bad usage. It will make the user think it will check everything here. This should be improved in the next version.

Quoted 13 lines
Quoted 13 lines
builtin/refs.c +1 -1
@@ -75,7 +77,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "strict", &fsck_refs_options.strict, N_("enable strict checking")),
 		OPT_END(),
 	};
-	int ret;
+	int ret = 0;
 
 	argc = parse_options(argc, argv, prefix, options, verify_usage, 0);
 	if (argc)
@@ -84,9 +86,14 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	git_config(git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(the_repository);
 
Quoted 11 lines

builtin/refs.c static int cmd_refs_verify(int argc, const char **argv, const char *prefix) · line 93

Quoted 6 lines
builtin/refs.c +5 -1
-	ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
+	worktrees = get_worktrees();
+	for (p = worktrees; *p; p++) {
+		struct worktree *wt = *p;
+		ret += refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options);
+	}

I think it is more customary to say ret |= instead of ref +=. Otherwise we could at least in theory wrap around and even land at ret == 0, even though this is quite unlikely.

I agree here. I will improve this in the next version.

[snip]

Quoted 18 lines
Quoted 18 lines
refs/files-backend.c +10 -2
@@ -3600,8 +3600,16 @@ static int files_fsck(struct ref_store *ref_store,
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_READ, "fsck");
 
-	return files_fsck_refs(ref_store, o) |
-	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+	int ret = files_fsck_refs(ref_store, o);
+
+	/*
+	 * packed-refs should only be checked once because it is shared
+	 * between all worktrees.
+	 */
+	if (!strcmp(ref_store->gitdir, ref_store->repo->gitdir))
+		ret += refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+
+	return ret;
 }
 
Quoted 7 lines

refs/files-backend.c static int files_fsck(struct ref_store *ref_store, · line 3615

Quoted 1 line
refs/files-backend.c +0 -0
 struct ref_storage_be refs_be_files = {

What is the current behaviour? Is it that we verify the packed-refs file multiple times, or rather that we call packed_ref_store->be->fsck() many times even though we know it won't do anything for anything except for the main worktree?

That's a good question. I think the second is the current behaviour. We will call packed_ref_store->be->fsck() many times. I understand what you mean here, we just put the check into packed_ref_store->be->fsck() function.

Quoted 5 lines

If it is the former I very much agree that we should make this conditional. If it's the latter I'm more in the camp of letting it be such that if worktrees were to ever gain support for "packed-refs" we wouldn't have to change anything.

I agree.

Quoted 4 lines

In any case, as proposed I think it would make sense to evict this into a standalone commit such that these details can be explained in the commit message.

Yes, the current commit message lacks of details.

Quoted 1 line

Patrick

Thanks,
Jialuo

#102 lore raw

Re: [PATCH v5 2/9] builtin/refs: support multiple worktrees check for refs.

Quoted 44 lines · shejialuo
Quoted 14 lines · Patrick Steinhardt
Quoted 5 lines · shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees. Use "get_worktrees" and "get_worktree_ref_store" to check refs under the worktrees.

Makes sense.

Quoted 4 lines

Because we should only check once for "packed-refs", let's call the fsck function for packed-backend when in the main worktree. In order to know which directory we check, we should default print this information instead of specifying "--verbose".

Quoted 8 lines

This change should likely be evicted into its own commit with a bit more explanation.

Quoted 2 lines

It's not suitable to print these information to the stderr. So, change to stdout.

This one, too. Why exactly is in not suitable to print to stderr?

I am sorry for the confusion. We should not print which directory we check here into stderr. Because I think this will make test script contain many unrelated info when using "git refs verify 2>err".

The reason here is when checking the consistency of refs in multiple worktrees. The ref name could be repeat. For example, worktree A has its own ref called "test" under ".git/worktrees/A/refs/worktree/test" and worktree B has its own ref still called "test" under ".git/worktrees/B/refs/worktree/test".

However, the refname would be printed to "refs/worktree/test". It will make the user confused which "refs/worktree/test" is checked. So, we should print this information like:

Checking references consistency in .git
...
checking references consistency in .git/worktrees/A
...
checking references consistency in .git/worktrees/B

Quoted 3 lines

However, when writing this, I feel a ".git" is a bad usage. It will make the user think it will check everything here. This should be improved in the next version.

But wouldn't it be the better solution if we printed the fully-qualified reference name "worktrees/worktree/refs/worktree/test" instead? That would remove the need to say which directory we're currently verifying in the first place.

Patrick

author
#103 lore raw

Re: [PATCH v5 2/9] builtin/refs: support multiple worktrees check for refs.

On Mon, Oct 07, 2024 at 11:16:19AM +0200, Patrick Steinhardt wrote:

[snip]

Quoted 10 lines
Quoted 10 lines

However, the refname would be printed to "refs/worktree/test". It will make the user confused which "refs/worktree/test" is checked. So, we should print this information like:

Checking references consistency in .git
...
checking references consistency in .git/worktrees/A
...
checking references consistency in .git/worktrees/B

Quoted 9 lines
Quoted 3 lines

However, when writing this, I feel a ".git" is a bad usage. It will make the user think it will check everything here. This should be improved in the next version.

But wouldn't it be the better solution if we printed the fully-qualified reference name "worktrees/worktree/refs/worktree/test" instead? That would remove the need to say which directory we're currently verifying in the first place.

Good idea. I will use this way in the next version.

Quoted 1 line

Patrick

Thanks

author
#104 lore raw

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

In reply to shejialuo

"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.

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 45 ++++++++++++++++++++++++
 t/t0602-reffiles-fsck.sh      | 66 +++++++++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index 68a2801f15..22c385ea22 100644
@@ -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.
 
fsck.h +1 -0
index 500b4c04d2..0d99a87911 100644
@@ -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) \
refs/files-backend.c +45 -0

57 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +66 -0

70 lines not on this page · raw message

Signature · 1 line
2.46.2
#105 lore raw

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

Quoted 4 lines · shejialuo

"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.

Quoted 4 lines

"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

author
#106 lore raw

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

Quoted 11 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

"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.

Quoted 9 lines
Quoted 4 lines

"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?

Quoted 1 line

Patrick

Thanks,
Jialuo

#107 lore raw

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

Quoted 7 lines · shejialuo
Quoted 6 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

"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.

Quoted 8 lines
Quoted 4 lines

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

author
#108 lore raw

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

Quoted 13 lines · Patrick Steinhardt
Quoted 12 lines · shejialuo
Quoted 10 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

"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.

Quoted 8 lines
Quoted 3 lines

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.

Quoted 1 line

Patrick

#109 lore raw

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

In reply to shejialuo

shejialuo <[email protected]> writes:

[snip]

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3533

Quoted 7 lines
refs/files-backend.c +7 -0
+	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]

author
#110 lore raw

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

Quoted 4 lines · Karthik Nayak

shejialuo <[email protected]> writes:

[snip]

Quoted 13 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3533

Quoted 7 lines
refs/files-backend.c +7 -0
+	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

#111 lore raw

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

Quoted 19 lines · shejialuo
Quoted 4 lines · Karthik Nayak

shejialuo <[email protected]> writes:

[snip]

Quoted 13 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3533

Quoted 7 lines
refs/files-backend.c +7 -0
+	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.

Quoted 4 lines

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.

#112 lore raw

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

Quoted 30 lines · Junio C Hamano
Quoted 19 lines · shejialuo
Quoted 4 lines · Karthik Nayak

shejialuo <[email protected]> writes:

[snip]

Quoted 13 lines

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3533

Quoted 7 lines
refs/files-backend.c +7 -0
+	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.

Quoted 4 lines

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.

Quoted 6 lines

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.

Quoted 7 lines

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.

Quoted 4 lines

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

author
#113 lore raw

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

On Wed, Oct 09, 2024 at 10:05:19AM +0200, Patrick Steinhardt wrote:

[snip]

Quoted 11 lines
Quoted 4 lines

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.

Quoted 8 lines
Quoted 4 lines

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.

Quoted 1 line

Patrick

Thanks,
Jialuo

#114 lore raw

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

Quoted 6 lines · shejialuo
Quoted 5 lines · Patrick Steinhardt
Quoted 4 lines

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()?

Quoted 7 lines
Quoted 3 lines

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

#115 lore raw

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

Quoted 11 lines · Patrick Steinhardt
Quoted 10 lines · shejialuo
Quoted 8 lines · Patrick Steinhardt
Quoted 4 lines

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.

Quoted 6 lines
Quoted 3 lines

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.

author
#116 lore raw

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

In reply to Junio C Hamano
Quoted 21 lines · Junio C Hamano
Quoted 19 lines · shejialuo
Quoted 17 lines · Karthik Nayak

shejialuo <[email protected]> writes:

[snip]

refs/files-backend.c typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, · line 3533

Quoted 7 lines
refs/files-backend.c +7 -0
+	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.

Quoted 8 lines
Quoted 4 lines

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.

Quoted 6 lines

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.

Quoted 2 lines

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.

Quoted 9 lines

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.

Quoted 1 line

Thanks.

Thanks,
Jialuo

author
#117 lore raw

PATCH v5 4/9 ref: add more strict checks for regular refs

In reply to shejialuo

We have already used "parse_loose_ref_contents" function to check whether the ref content is valid in files backend. However, by using "parse_loose_ref_contents", we allow the ref's content to end with garbage or without a newline.

Even though we never create such loose refs ourselves, we have accepted such loose refs. So, it is entirely possible that some third-party tools may rely on such loose refs being valid. We should not report an error fsck message at current. We should notify the users about such "curiously formatted" loose refs so that adequate care is taken before we decide to tighten the rules in the future.

And it's not suitable either to report a warn fsck message to the user. We don't yet want the "--strict" flag that controls this bit to end up generating errors for such weirdly-formatted reference contents, as we first want to assess whether this retroactive tightening will cause issues for any tools out there. It may cause compatibility issues which may break the repository. So we add the "unofficialFormattedRef(INFO)" fsck message to represent the situation where the ref format is not officially created by us and notify the users it may become an error in the future.

It might appear that we can't provide the user with any warnings by using FSCK_INFO. However, in "fsck.c::fsck_vreport", we will convert FSCK_INFO to FSCK_WARN and we can still warn the user about these situations when using "git refs verify" without introducing compatibility issues.

---
 Documentation/fsck-msgids.txt |  8 +++++
 fsck.h                        |  1 +
 refs.c                        |  2 +-
 refs/files-backend.c          | 26 +++++++++++++--
 refs/refs-internal.h          |  2 +-
 t/t0602-reffiles-fsck.sh      | 59 +++++++++++++++++++++++++++++++++++
 6 files changed, 93 insertions(+), 5 deletions(-)
Documentation/fsck-msgids.txt +8 -0
index 22c385ea22..e310b5bce9 100644
@@ -179,6 +179,14 @@
 `unknownType`::
 	(ERROR) Found an unknown object type.
 
+`unofficialFormattedRef`::
+	(INFO) The content of a loose ref file is not in the official
+	format such as not having a LF at the end or having trailing
+	garbage. As valid implementations of Git never created such a
+	loose ref file, it may become an error in the future. Report
+	to the [email protected] mailing list if you see this error,
+	as we need to know what tools created such a file.
+
 `unterminatedHeader`::
 	(FATAL) Missing end-of-line in the object header.
 
fsck.h +1 -0
index 0d99a87911..7420add5c0 100644
@@ -85,6 +85,7 @@ enum fsck_msg_type {
 	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
+	FUNC(UNOFFICIAL_FORMATTED_REF, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
 
refs.c +1 -1
index 5f729ed412..6ba1bb1aa1 100644
@@ -1788,7 +1788,7 @@ static int refs_read_special_head(struct ref_store *ref_store,
 	}
 
 	result = parse_loose_ref_contents(ref_store->repo->hash_algo, content.buf,
-					  oid, referent, type, failure_errno);
+					  oid, referent, type, NULL, failure_errno);
 
 done:
 	strbuf_release(&full_path);
refs/files-backend.c +23 -3
index 35b3fa983e..b2a790c884 100644
@@ -568,7 +568,7 @@ static int read_ref_internal(struct ref_store *ref_store, const char *refname,
 	buf = sb_contents.buf;
 
 	ret = parse_loose_ref_contents(ref_store->repo->hash_algo, buf,
-				       oid, referent, type, &myerr);
+				       oid, referent, type, NULL, &myerr);
 
 out:
 	if (ret && !myerr)
@@ -605,7 +605,7 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno)
+			     const char **trailing, int *failure_errno)
 {
 	const char *p;
 	if (skip_prefix(buf, "ref:", &buf)) {
@@ -627,6 +627,10 @@ int parse_loose_ref_contents(const struct git_hash_algo *algop,
 		*failure_errno = EINVAL;
 		return -1;
 	}
+
+	if (trailing)
+		*trailing = p;
+
 	return 0;
 }
 
@@ -3513,6 +3517,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 	struct strbuf referent = STRBUF_INIT;
 	struct strbuf refname = STRBUF_INIT;
 	struct fsck_ref_report report = { 0 };
+	const char *trailing = NULL;
 	unsigned int type = 0;
 	int failure_errno = 0;
 	struct object_id oid;
@@ -3533,7 +3538,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 
 	if (parse_loose_ref_contents(ref_store->repo->hash_algo,
 				     ref_content.buf, &oid, &referent,
-				     &type, &failure_errno)) {
+				     &type, &trailing, &failure_errno)) {
 		strbuf_rtrim(&ref_content);
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_CONTENT,
@@ -3541,6 +3546,21 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "misses LF at the end");
+			goto cleanup;
+		}
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "has trailing garbage: '%s'", trailing);
+			goto cleanup;
+		}
+	}
+
 cleanup:
 	strbuf_release(&refname);
 	strbuf_release(&ref_content);
refs/refs-internal.h +1 -1
index 2313c830d8..73b05f971b 100644
@@ -715,7 +715,7 @@ struct ref_store {
 int parse_loose_ref_contents(const struct git_hash_algo *algop,
 			     const char *buf, struct object_id *oid,
 			     struct strbuf *referent, unsigned int *type,
-			     int *failure_errno);
+			     const char **trailing, int *failure_errno);
 
 /*
  * Fill in the generic part of refs and add it to our collection of
t/t0602-reffiles-fsck.sh +59 -0

77 lines not on this page · raw message

Signature · 1 line
2.46.2
#118 lore raw

Re: [PATCH v5 4/9] ref: add more strict checks for regular refs

Quoted 6 lines · shejialuo
Documentation/fsck-msgids.txt +0 -0
index 22c385ea22..e310b5bce9 100644
@@ -179,6 +179,14 @@
 `unknownType`::

Documentation/fsck-msgids.txt line 189

Quoted 10 lines
Documentation/fsck-msgids.txt +8 -0
 	(ERROR) Found an unknown object type.
 
+`unofficialFormattedRef`::
+	(INFO) The content of a loose ref file is not in the official
+	format such as not having a LF at the end or having trailing
+	garbage. As valid implementations of Git never created such a
+	loose ref file, it may become an error in the future. Report
+	to the [email protected] mailing list if you see this error,
+	as we need to know what tools created such a file.
+

I find "unofficial" to be a tad weird. Do we rather want to say something like "badRefTrailingGarbage"?

Quoted 9 lines
Documentation/fsck-msgids.txt +5 -0
@@ -3541,6 +3546,21 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "misses LF at the end");

Documentation/fsck-msgids.txt static int files_fsck_refs_content(struct ref_store *ref_store, · line 3563

Quoted 10 lines
Documentation/fsck-msgids.txt +10 -0
+			goto cleanup;
+		}
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "has trailing garbage: '%s'", trailing);
+			goto cleanup;
+		}
+	}
+

I think we should discern these two error cases and provide different message IDs.

Patrick

author
#119 lore raw

Re: [PATCH v5 4/9] ref: add more strict checks for regular refs

Quoted 9 lines · Patrick Steinhardt
Quoted 8 lines · shejialuo
Documentation/fsck-msgids.txt +0 -0
index 22c385ea22..e310b5bce9 100644
@@ -179,6 +179,14 @@
 `unknownType`::
 	(ERROR) Found an unknown object type.
 
Quoted 12 lines

Documentation/fsck-msgids.txt line 189

Quoted 8 lines
Documentation/fsck-msgids.txt +8 -0
+`unofficialFormattedRef`::
+	(INFO) The content of a loose ref file is not in the official
+	format such as not having a LF at the end or having trailing
+	garbage. As valid implementations of Git never created such a
+	loose ref file, it may become an error in the future. Report
+	to the [email protected] mailing list if you see this error,
+	as we need to know what tools created such a file.
+

I find "unofficial" to be a tad weird. Do we rather want to say something like "badRefTrailingGarbage"?

Well, I will answer this question just in below question together.

Quoted 14 lines
Quoted 14 lines
Documentation/fsck-msgids.txt +10 -0
@@ -3541,6 +3546,21 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "misses LF at the end");
+			goto cleanup;
+		}
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
Quoted 9 lines

Documentation/fsck-msgids.txt static int files_fsck_refs_content(struct ref_store *ref_store, · line 3563

Quoted 5 lines
Documentation/fsck-msgids.txt +5 -0
+					      "has trailing garbage: '%s'", trailing);
+			goto cleanup;
+		}
+	}
+

I think we should discern these two error cases and provide different message IDs.

Actually, in the previous versions, I have mapped one message id to one error case. But, in the v4, Junio asked a question

Not limited to this patch, but isn't fsck_report_ref() misdesigned, or is it just they are used poorly in these patches? In these two callsites, the message string parameter does not give any more information than what the FSCK_MSG_* enum gives.

That is what I meant by "misdesigned"---if one message enum always corresponds to one human-readable message, there is not much point in forcing callers to supply both, is there?

In my opinion, we should have only one case here for trailing garbage and not end with a newline. When writing the code, I chose the name "unofficialFormattedRef" for the following reason:

1. If we use two message ids here, for every message id, we need write to info the user "please report this to git mailing list".

2. If we decide to make this as an error. We could just classify them into "badRefContent" message category.

3. The semantic is correct here, they are truly curious formatted refs, and eventually we will give the info to the user what is curious.

So, I think we should not always map one message to one error case.

Quoted 1 line

Patrick

Thanks,
Jialuo

#120 lore raw

Re: [PATCH v5 4/9] ref: add more strict checks for regular refs

Quoted 49 lines · shejialuo
Quoted 19 lines · Patrick Steinhardt
Quoted 18 lines · shejialuo
Documentation/fsck-msgids.txt +10 -0
index 22c385ea22..e310b5bce9 100644
@@ -3541,6 +3546,21 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 		goto cleanup;
 	}
 
+	if (!(type & REF_ISSYMREF)) {
+		if (!*trailing) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+					      "misses LF at the end");
+			goto cleanup;
+		}
+		if (*trailing != '\n' || *(trailing + 1)) {
+			ret = fsck_report_ref(o, &report,
+					      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
Quoted 9 lines

Documentation/fsck-msgids.txt static int files_fsck_refs_content(struct ref_store *ref_store, · line 3563

Quoted 5 lines
Documentation/fsck-msgids.txt +5 -0
+					      "has trailing garbage: '%s'", trailing);
+			goto cleanup;
+		}
+	}
+

I think we should discern these two error cases and provide different message IDs.

Actually, in the previous versions, I have mapped one message id to one error case. But, in the v4, Junio asked a question

Not limited to this patch, but isn't fsck_report_ref() misdesigned, or is it just they are used poorly in these patches? In these two callsites, the message string parameter does not give any more information than what the FSCK_MSG_* enum gives.

That is what I meant by "misdesigned"---if one message enum always corresponds to one human-readable message, there is not much point in forcing callers to supply both, is there?

In my opinion, we should have only one case here for trailing garbage and not end with a newline. When writing the code, I chose the name "unofficialFormattedRef" for the following reason:

1. If we use two message ids here, for every message id, we need write to info the user "please report this to git mailing list".

Quoted 8 lines

2. If we decide to make this as an error. We could just classify them into "badRefContent" message category.

3. The semantic is correct here, they are truly curious formatted refs, and eventually we will give the info to the user what is curious.

So, I think we should not always map one message to one error case.

From my point of view the error codes should be the single source of truth, as this is what a user can use to disable specific checks. So if one code maps to multiple messages they have the problem that they can only disable all of those messages.

I don't disagree with what Junio is saying. It is somewhat duplicate that the user has to pass both a code and a message in the current form-- it should be sufficient for them to pass the code, and the message can then e.g. be extracted from a central array that maps codes to messages.

But you can also make the reverse argument: messages can be dynamic, so that the caller may include additional details around why specfically the check failed. The code and message would still be 1:1, but we may include additional details like that to guide the user.

Patrick

author
#121 lore raw

Re: [PATCH v5 4/9] ref: add more strict checks for regular refs

On Mon, Oct 07, 2024 at 11:25:17AM +0200, Patrick Steinhardt wrote:

[snip]

Quoted 27 lines
Quoted 27 lines

Actually, in the previous versions, I have mapped one message id to one error case. But, in the v4, Junio asked a question

Not limited to this patch, but isn't fsck_report_ref() misdesigned, or is it just they are used poorly in these patches? In these two callsites, the message string parameter does not give any more information than what the FSCK_MSG_* enum gives.

That is what I meant by "misdesigned"---if one message enum always corresponds to one human-readable message, there is not much point in forcing callers to supply both, is there?

In my opinion, we should have only one case here for trailing garbage and not end with a newline. When writing the code, I chose the name "unofficialFormattedRef" for the following reason:

1. If we use two message ids here, for every message id, we need write to info the user "please report this to git mailing list".

2. If we decide to make this as an error. We could just classify them into "badRefContent" message category.

3. The semantic is correct here, they are truly curious formatted refs, and eventually we will give the info to the user what is curious.

Quoted 7 lines
Quoted 1 line

So, I think we should not always map one message to one error case.

From my point of view the error codes should be the single source of truth, as this is what a user can use to disable specific checks. So if one code maps to multiple messages they have the problem that they can only disable all of those messages.

Thanks for your remind here. I totally forgot this. I have changed my mind now, we should use one to one mapping here. As you said, if we do not, we will give the user the bad experience.

Quoted 11 lines

I don't disagree with what Junio is saying. It is somewhat duplicate that the user has to pass both a code and a message in the current form-- it should be sufficient for them to pass the code, and the message can then e.g. be extracted from a central array that maps codes to messages.

But you can also make the reverse argument: messages can be dynamic, so that the caller may include additional details around why specfically the check failed. The code and message would still be 1:1, but we may include additional details like that to guide the user.

Yes, I will refactor the "fsck_report" to allow the user pass the "NULL" message if the fsck message id is clear enough to indicate the error case.

So, more things to do here.

Quoted 1 line

Patrick

Thanks,
Jialuo

author
#122 lore raw

PATCH v5 5/9 ref: add basic symref content check for files backend

In reply to shejialuo

We have code that checks regular ref contents, but we do not yet check the contents of symbolic refs. By using "parse_loose_ref_content" for symbolic refs, we will get the information of the "referent".

We do not need to check the "referent" by opening the file. This is because if "referent" exists in the file system, we will eventually check its correctness by inspecting every file in the "refs" directory. If the "referent" does not exist in the filesystem, this is OK as it is seen as the dangling symref.

So we just need to check the "referent" string content. A regular could be accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. However, we always write a single SP after "ref:" and a single LF after the refname. It may seem that we should report a fsck error message when the "referent" does not apply above rules and we should not be so aggressive because third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following "referent":

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

When introducing the regular ref content checks, we created a new fsck message "unofficialFormattedRef" which exactly represents above situation. So we will reuse this fsck message to write checks to info the user about these situations.

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

And we introduce a new "badReferent(ERROR)" fsck message to report above errors by using "ref.c::check_refname_format". But we cannot just pass the "referent" to this function because the "referent" might contain some whitespaces which will cause "check_refname_format" failing.

In order to add checks, we will do the following things:

1. Record the untrimmed length "orig_len" and untrimmed last byte "orig_last_byte".
2. Use "strbuf_rtrim" to trim the whitespaces or newlines to make sure "check_refname_format" won't be failed by them.
3. Use "orig_len" and "orig_last_byte" to check whether the "referent" misses '\n' at the end or it has trailing whitespaces or newlines.

---
 Documentation/fsck-msgids.txt |  3 ++
 fsck.h                        |  1 +
 refs/files-backend.c          | 40 +++++++++++++++
 t/t0602-reffiles-fsck.sh      | 97 +++++++++++++++++++++++++++++++++++
 4 files changed, 141 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index e310b5bce9..e0e4519334 100644
@@ -28,6 +28,9 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
+`badReferent`::
+	(ERROR) The referent of a ref is invalid.
+
 `badTagName`::
 	(INFO) A tag has an invalid format.
 
fsck.h +1 -0
index 7420add5c0..979d75cb53 100644
@@ -34,6 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
+	FUNC(BAD_REFERENT, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
refs/files-backend.c +40 -0
index b2a790c884..57ac466b64 100644
@@ -3508,6 +3508,43 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  const char *refs_check_dir,
 				  struct dir_iterator *iter);
 
+static int files_fsck_symref_target(struct fsck_options *o,
+				    struct fsck_ref_report *report,
+				    struct strbuf *referent)
+{
+	char orig_last_byte;
+	size_t orig_len;
+	int ret = 0;
+
+	orig_len = referent->len;
+	orig_last_byte = referent->buf[orig_len - 1];
+	strbuf_rtrim(referent);
+
+	if (check_refname_format(referent->buf, 0)) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_BAD_REFERENT,
+				      "points to invalid refname '%s'", referent->buf);
+		goto out;
+	}
+
+
+	if (referent->len == orig_len ||
+	    (referent->len < orig_len && orig_last_byte != '\n')) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+				      "misses LF at the end");
+	}
+
+	if (referent->len != orig_len && referent->len != orig_len - 1) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_UNOFFICIAL_FORMATTED_REF,
+				      "has trailing whitespaces or newlines");
+	}
+
+out:
+	return ret;
+}
+
 static int files_fsck_refs_content(struct ref_store *ref_store,
 				   struct fsck_options *o,
 				   const char *refs_check_dir,
@@ -3559,6 +3596,9 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
 					      "has trailing garbage: '%s'", trailing);
 			goto cleanup;
 		}
+	} else {
+		ret = files_fsck_symref_target(o, &report, &referent);
+		goto cleanup;
 	}
 
 cleanup:
t/t0602-reffiles-fsck.sh +97 -0

101 lines not on this page · raw message

Signature · 1 line
2.46.2
#123 lore raw

Re: [PATCH v5 5/9] ref: add basic symref content check for files backend

Quoted 11 lines · shejialuo

We have code that checks regular ref contents, but we do not yet check the contents of symbolic refs. By using "parse_loose_ref_content" for symbolic refs, we will get the information of the "referent".

We do not need to check the "referent" by opening the file. This is because if "referent" exists in the file system, we will eventually check its correctness by inspecting every file in the "refs" directory. If the "referent" does not exist in the filesystem, this is OK as it is seen as the dangling symref.

So we just need to check the "referent" string content. A regular could

seems like we're missing the noun here, a regular what?

Quoted 9 lines

be accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. However, we always write a single SP after "ref:" and a single LF after the refname. It may seem that we should report a fsck error message when the "referent" does not apply above rules and we should not be so aggressive because third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following "referent":

Quoted 9 lines

1. "ref: refs/heads/master "
2. "ref: refs/heads/master \n \n"
3. "ref: refs/heads/master\n\n"

When introducing the regular ref content checks, we created a new fsck message "unofficialFormattedRef" which exactly represents above situation. So we will reuse this fsck message to write checks to info the user about these situations.

Plus to what Patrick said in the previous commit, it would be nice to separate these issues with different message IDs.

Quoted 11 lines

But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)".

1. "ref: refs/heads/master garbage\n"
2. "ref: refs/heads/master \n\n\n garbage "

And we introduce a new "badReferent(ERROR)" fsck message to report above errors by using "ref.c::check_refname_format". But we cannot just pass the "referent" to this function because the "referent" might contain some whitespaces which will cause "check_refname_format" failing.

It would be nice if you could elaborate here, or rather restructure to say something like..

Since 'check_refname_format' doesn't work with whitespaces, we use the trimmed version of 'referent' with the function.

[snip]

author
#124 lore raw

Re: [PATCH v5 5/9] ref: add basic symref content check for files backend

Quoted 12 lines · Karthik Nayak
Quoted 10 lines · shejialuo

We have code that checks regular ref contents, but we do not yet check the contents of symbolic refs. By using "parse_loose_ref_content" for symbolic refs, we will get the information of the "referent".

We do not need to check the "referent" by opening the file. This is because if "referent" exists in the file system, we will eventually check its correctness by inspecting every file in the "refs" directory. If the "referent" does not exist in the filesystem, this is OK as it is seen as the dangling symref.

Quoted 4 lines
Quoted 1 line

So we just need to check the "referent" string content. A regular could

seems like we're missing the noun here, a regular what?

It should be "a regular ref". I copied the original commit message and may carelessly type "daw" in vim to delete the "ref". Thanks.

author
#125 lore raw

PATCH v5 6/9 ref: add escape check for the referent of symref

In reply to shejialuo

Ideally, we want to the users use "git symbolic-ref" to create symrefs instead of writing raw contents into the filesystem. However, "git symbolic-ref" is strict with the refname but not strict with the referent. For example, we can make the "referent" located at the "$(gitdir)/logs/aaa" and manually write the content into this where we can still successfully parse this symref by using "git rev-parse".

  $ git init repo && cd repo && git commit --allow-empty -mx
  $ git symbolic-ref refs/heads/test logs/aaa
  $ echo $(git rev-parse HEAD) > .git/logs/aaa
  $ git rev-parse test

We may need to add some restrictions for "referent" parameter when using "git symbolic-ref" to create symrefs because ideally all the nonpeudo-refs should be located under the "refs" directory and we may tighten this in the future.

In order to tell the user we may tighten the "escape" situation, create a new fsck message "escapeReferent" to notify the user that this may become an error in the future.

---
 Documentation/fsck-msgids.txt |  8 ++++++++
 fsck.h                        |  1 +
 refs/files-backend.c          |  7 +++++++
 t/t0602-reffiles-fsck.sh      | 18 ++++++++++++++++++
 4 files changed, 34 insertions(+)
Documentation/fsck-msgids.txt +8 -0
index e0e4519334..223974057d 100644
@@ -52,6 +52,14 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
+`escapeReferent`::
+	(INFO) The referent of a symref is outside the "ref" directory.
+	Although we allow create a symref pointing to the referent which
+	is outside the "ref" by using `git symbolic-ref`, we may tighten
+	the rule in the future. Report to the [email protected]
+	mailing list if you see this error, as we need to know what tools
+	created such a file.
+
 `extraHeaderEntry`::
 	(IGNORE) Extra headers found after `tagger`.
 
fsck.h +1 -0
index 979d75cb53..5ecee0fda5 100644
@@ -80,6 +80,7 @@ enum fsck_msg_type {
 	FUNC(LARGE_PATHNAME, WARN) \
 	/* infos (reported as warnings, but ignored by default) */ \
 	FUNC(BAD_FILEMODE, INFO) \
+	FUNC(ESCAPE_REFERENT, INFO) \
 	FUNC(GITMODULES_PARSE, INFO) \
 	FUNC(GITIGNORE_SYMLINK, INFO) \
 	FUNC(GITATTRIBUTES_SYMLINK, INFO) \
refs/files-backend.c +7 -0
index 57ac466b64..bd215c8d08 100644
@@ -3520,6 +3520,13 @@ static int files_fsck_symref_target(struct fsck_options *o,
 	orig_last_byte = referent->buf[orig_len - 1];
 	strbuf_rtrim(referent);
 
+	if (!starts_with(referent->buf, "refs/")) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_ESCAPE_REFERENT,
+				      "referent '%s' is outside of refs/",
+				      referent->buf);
+	}
+
 	if (check_refname_format(referent->buf, 0)) {
 		ret = fsck_report_ref(o, report,
 				      FSCK_MSG_BAD_REFERENT,
t/t0602-reffiles-fsck.sh +18 -0
index 718f6abb71..585f562245 100755
@@ -370,4 +370,22 @@ test_expect_success 'textual symref content should be checked (aggregate)' '
 	test_cmp expect sorted_err
 '
 
+test_expect_success 'textual symref should be checked whether it is escaped' '
+	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" &&
+
+	printf "ref: refs-back/heads/main\n" >$branch_dir_prefix/branch-bad-1 &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/heads/branch-bad-1: escapeReferent: referent '\''refs-back/heads/main'\'' is outside of refs/
+	EOF
+	rm $branch_dir_prefix/branch-bad-1 &&
+	test_cmp expect err
+'
+
 test_done
Signature · 1 line
2.46.2
#126 lore raw

Re: [PATCH v5 6/9] ref: add escape check for the referent of symref

Quoted 11 lines · shejialuo

Ideally, we want to the users use "git symbolic-ref" to create symrefs instead of writing raw contents into the filesystem. However, "git symbolic-ref" is strict with the refname but not strict with the referent. For example, we can make the "referent" located at the "$(gitdir)/logs/aaa" and manually write the content into this where we can still successfully parse this symref by using "git rev-parse".

  $ git init repo && cd repo && git commit --allow-empty -mx
  $ git symbolic-ref refs/heads/test logs/aaa
  $ echo $(git rev-parse HEAD) > .git/logs/aaa
  $ git rev-parse test

Oh, curious. This should definitely be tightened in git-symbolic-ref(1) itself. The target should either be a root ref or something starting with "refs/". Anyway, that is of course outside of the scope of this patch series.

Quoted 4 lines

We may need to add some restrictions for "referent" parameter when using "git symbolic-ref" to create symrefs because ideally all the nonpeudo-refs should be located under the "refs" directory and we may tighten this in the future.

Agreed.

Quoted 19 lines

In order to tell the user we may tighten the "escape" situation, create a new fsck message "escapeReferent" to notify the user that this may become an error in the future.

---
 Documentation/fsck-msgids.txt |  8 ++++++++
 fsck.h                        |  1 +
 refs/files-backend.c          |  7 +++++++
 t/t0602-reffiles-fsck.sh      | 18 ++++++++++++++++++
 4 files changed, 34 insertions(+)
Documentation/fsck-msgids.txt +0 -0
index e0e4519334..223974057d 100644

Documentation/fsck-msgids.txt line 56

Quoted 5 lines
Documentation/fsck-msgids.txt +2 -0
@@ -52,6 +52,14 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
+`escapeReferent`::
+	(INFO) The referent of a symref is outside the "ref" directory.

Proposal: 'The referent of a symbolic reference points neither to a root reference nor to a reference starting with "refs/".'

I'd also rename this to e.g. "symrefTargetIsNotAReference" or something like that, because it's not really about whether or not the referent is "escaping". It's a bit of a mouthful, but I don't really have a better name. So feel free to pick something different that describes the error better.

Quoted 8 lines
refs/files-backend.c +0 -0
index 57ac466b64..bd215c8d08 100644
@@ -3520,6 +3520,13 @@ static int files_fsck_symref_target(struct fsck_options *o,
 	orig_last_byte = referent->buf[orig_len - 1];
 	strbuf_rtrim(referent);
 

refs/files-backend.c static int files_fsck_symref_target(struct fsck_options *o, · line 3532

Quoted 10 lines
refs/files-backend.c +7 -0
+	if (!starts_with(referent->buf, "refs/")) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_ESCAPE_REFERENT,
+				      "referent '%s' is outside of refs/",
+				      referent->buf);
+	}
+
 	if (check_refname_format(referent->buf, 0)) {
 		ret = fsck_report_ref(o, report,
 				      FSCK_MSG_BAD_REFERENT,

This check is invalid, because referents can also point to root refs. So you should probably also add a call to is_root_ref() here.

We also have is_pseudo_ref(), and one might be tempted to also allow that. But pseudo refs aren't proper refs, so I'd argue that a symref pointing to a pseudo ref is invalid, too.

Patrick

author
#127 lore raw

Re: [PATCH v5 6/9] ref: add escape check for the referent of symref

Quoted 13 lines · Patrick Steinhardt
Quoted 11 lines · shejialuo

Ideally, we want to the users use "git symbolic-ref" to create symrefs instead of writing raw contents into the filesystem. However, "git symbolic-ref" is strict with the refname but not strict with the referent. For example, we can make the "referent" located at the "$(gitdir)/logs/aaa" and manually write the content into this where we can still successfully parse this symref by using "git rev-parse".

  $ git init repo && cd repo && git commit --allow-empty -mx
  $ git symbolic-ref refs/heads/test logs/aaa
  $ echo $(git rev-parse HEAD) > .git/logs/aaa
  $ git rev-parse test
Quoted 5 lines

Oh, curious. This should definitely be tightened in git-symbolic-ref(1) itself. The target should either be a root ref or something starting with "refs/". Anyway, that is of course outside of the scope of this patch series.

I am curious here too when I did experiments when writing the code. Because Junio have told me this could happen, so I dive into this. However, it's not reasonable. If we want to tighten the rule, we need to also let "git symbolic-ref" to align with the behavior. That's another question though.

[snip]

Quoted 8 lines
Quoted 8 lines
Documentation/fsck-msgids.txt +0 -0
index e0e4519334..223974057d 100644
@@ -52,6 +52,14 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
Quoted 6 lines

Documentation/fsck-msgids.txt line 56

Quoted 2 lines
Documentation/fsck-msgids.txt +2 -0
+`escapeReferent`::
+	(INFO) The referent of a symref is outside the "ref" directory.

Proposal: 'The referent of a symbolic reference points neither to a root reference nor to a reference starting with "refs/".'

That's much better.

Quoted 6 lines

I'd also rename this to e.g. "symrefTargetIsNotAReference" or something like that, because it's not really about whether or not the referent is "escaping". It's a bit of a mouthful, but I don't really have a better name. So feel free to pick something different that describes the error better.

I guess "symrefTargetIsNotAReference" is a little too long. If we decide to convert it to error later. Why not just put it into the "badReferent" fsck message?

So, I do not think we need to rename. As I have talked about, we don't need to map error case to fsck message id one by one.

Quoted 8 lines
Quoted 8 lines
refs/files-backend.c +0 -0
index 57ac466b64..bd215c8d08 100644
@@ -3520,6 +3520,13 @@ static int files_fsck_symref_target(struct fsck_options *o,
 	orig_last_byte = referent->buf[orig_len - 1];
 	strbuf_rtrim(referent);
 
Quoted 14 lines

refs/files-backend.c static int files_fsck_symref_target(struct fsck_options *o, · line 3532

Quoted 10 lines
refs/files-backend.c +7 -0
+	if (!starts_with(referent->buf, "refs/")) {
+		ret = fsck_report_ref(o, report,
+				      FSCK_MSG_ESCAPE_REFERENT,
+				      "referent '%s' is outside of refs/",
+				      referent->buf);
+	}
+
 	if (check_refname_format(referent->buf, 0)) {
 		ret = fsck_report_ref(o, report,
 				      FSCK_MSG_BAD_REFERENT,

This check is invalid, because referents can also point to root refs. So you should probably also add a call to is_root_ref() here.

Thanks, I omit this situation here.

Quoted 4 lines

We also have is_pseudo_ref(), and one might be tempted to also allow that. But pseudo refs aren't proper refs, so I'd argue that a symref pointing to a pseudo ref is invalid, too.

I agree.

Quoted 1 line

Patrick

Thanks,
Jialuo

#128 lore raw

Re: [PATCH v5 6/9] ref: add escape check for the referent of symref

Quoted 9 lines · shejialuo
Quoted 7 lines · Patrick Steinhardt

On Sun, Sep 29, 2024 at 03:16:21PM +0800, shejialuo wrote:
I'd also rename this to e.g. "symrefTargetIsNotAReference" or something like that, because it's not really about whether or not the referent is "escaping". It's a bit of a mouthful, but I don't really have a better name. So feel free to pick something different that describes the error better.

Quoted 6 lines

I guess "symrefTargetIsNotAReference" is a little too long. If we decide to convert it to error later. Why not just put it into the "badReferent" fsck message?

So, I do not think we need to rename. As I have talked about, we don't need to map error case to fsck message id one by one.

Mostly because I disagree with this here. I think there should be a 1:1 mapping, and "badReferent" is too generic to provide that.

Patrick

author
#129 lore raw

PATCH v5 7/9 ref: enhance escape situation for worktrees

In reply to shejialuo

We do allow users to use "git symbolic-ref" to create symrefs which point to one of the linked worktrees from the primary worktree or one of the linked worktrees.

We should not info the user about the escape for above situation. So, enhance "files_fsck_symref_target" function to check whether the "referent" starts with the "worktrees/" to make sure that we won't warn the user when symrefs point to "referent" in the linked worktrees.

---
 refs/files-backend.c     |  5 +++--
 t/t0602-reffiles-fsck.sh | 34 +++++++++++++++++++++++++++++++++-
 2 files changed, 36 insertions(+), 3 deletions(-)
refs/files-backend.c +3 -2
index bd215c8d08..1182bca108 100644
@@ -3520,10 +3520,11 @@ static int files_fsck_symref_target(struct fsck_options *o,
 	orig_last_byte = referent->buf[orig_len - 1];
 	strbuf_rtrim(referent);
 
-	if (!starts_with(referent->buf, "refs/")) {
+	if (!starts_with(referent->buf, "refs/") &&
+	    !starts_with(referent->buf, "worktrees/")) {
 		ret = fsck_report_ref(o, report,
 				      FSCK_MSG_ESCAPE_REFERENT,
-				      "referent '%s' is outside of refs/",
+				      "referent '%s' is outside of refs/ or worktrees/",
 				      referent->buf);
 	}
 
t/t0602-reffiles-fsck.sh +33 -1
index 585f562245..936448f780 100755
@@ -382,10 +382,42 @@ test_expect_success 'textual symref should be checked whether it is escaped' '
 	printf "ref: refs-back/heads/main\n" >$branch_dir_prefix/branch-bad-1 &&
 	git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	warning: refs/heads/branch-bad-1: escapeReferent: referent '\''refs-back/heads/main'\'' is outside of refs/
+	warning: refs/heads/branch-bad-1: escapeReferent: referent '\''refs-back/heads/main'\'' is outside of refs/ or worktrees/
 	EOF
 	rm $branch_dir_prefix/branch-bad-1 &&
 	test_cmp expect err
 '
 
+test_expect_success 'textual symref escape check should work with worktrees' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	cd repo &&
+	test_commit default &&
+	git branch branch-1 &&
+	git branch branch-2 &&
+	git branch branch-3 &&
+	git worktree add ./worktree-1 branch-2 &&
+	git worktree add ./worktree-2 branch-3 &&
+
+	(
+		cd worktree-1 &&
+		git branch refs/worktree/w1-branch &&
+		git symbolic-ref refs/worktree/branch-4 refs/heads/branch-1 &&
+		git symbolic-ref refs/worktree/branch-5 worktrees/worktree-2/refs/worktree/w2-branch
+	) &&
+	(
+		cd worktree-2 &&
+		git branch refs/worktree/w2-branch &&
+		git symbolic-ref refs/worktree/branch-4 refs/heads/branch-1 &&
+		git symbolic-ref refs/worktree/branch-5 worktrees/worktree-1/refs/worktree/w1-branch
+	) &&
+
+
+	git symbolic-ref refs/heads/branch-5 worktrees/worktree-1/refs/worktree/w1-branch &&
+	git symbolic-ref refs/heads/branch-6 worktrees/worktree-2/refs/worktree/w2-branch &&
+
+	git refs verify 2>err &&
+	test_must_be_empty err
+'
+
 test_done
Signature · 1 line
2.46.2
#130 lore raw

Re: [PATCH v5 7/9] ref: enhance escape situation for worktrees

Quoted 8 lines · shejialuo

We do allow users to use "git symbolic-ref" to create symrefs which point to one of the linked worktrees from the primary worktree or one of the linked worktrees.

We should not info the user about the escape for above situation. So, enhance "files_fsck_symref_target" function to check whether the "referent" starts with the "worktrees/" to make sure that we won't warn the user when symrefs point to "referent" in the linked worktrees.

Shouldn't this commit be squashed into the former one, as it immediately fixes an edge case that was introduced with the parent commit?

Patrick

author
#131 lore raw

Re: [PATCH v5 7/9] ref: enhance escape situation for worktrees

Quoted 5 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

We do allow users to use "git symbolic-ref" to create symrefs which point to one of the linked worktrees from the primary worktree or one of the linked worktrees.

Quoted 8 lines
Quoted 4 lines

We should not info the user about the escape for above situation. So, enhance "files_fsck_symref_target" function to check whether the "referent" starts with the "worktrees/" to make sure that we won't warn the user when symrefs point to "referent" in the linked worktrees.

Shouldn't this commit be squashed into the former one, as it immediately fixes an edge case that was introduced with the parent commit?

I partially agree here. I don't think this is an edge case that was introduced with the parent commit. The reason why I use a new commit here is that I want to emphasis the behavior.

This is because Junio asked me in the v4 about "escapeReferent"

I am not sure starting this as ERROR is wise. Users and third-party tools make creative uses of the system and I cannot offhand think of an argument why it should be forbidden to create a symbolic link to our own HEAD or to some worktree-specific ref in another worktree.

Actually, I have never thought we could do this. So, this is my intention. But I do agree that this commit is highly relevant with the parent commit.

I will improve this in the next version.

Quoted 1 line

Patrick

author
#132 lore raw

PATCH v5 8/9 t0602: add ref content checks for worktrees

In reply to shejialuo

We have already added content tests, but we don't have tests when there are worktrees in the repository. Add a new test to test all the functionalities we have added for worktrees.

---
 t/t0602-reffiles-fsck.sh | 66 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)
t/t0602-reffiles-fsck.sh +66 -0
index 936448f780..97bbcd3f13 100755
@@ -420,4 +420,70 @@ test_expect_success 'textual symref escape check should work with worktrees' '
 	test_must_be_empty err
 '
 
+test_expect_success 'all textual symref checks should work with worktrees' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	cd repo &&
+	test_commit default &&
+	git branch branch-1 &&
+	git branch branch-2 &&
+	git branch branch-3 &&
+	git worktree add ./worktree-1 branch-2 &&
+	git worktree add ./worktree-2 branch-3 &&
+	worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+	worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+	(
+		cd worktree-1 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-1
+	) &&
+	(
+		cd worktree-2 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-1
+	) &&
+
+	bad_content_1=$(git rev-parse HEAD)x &&
+	bad_content_2=xfsazqfxcadas &&
+	bad_content_3=Xfsazqfxcadas &&
+
+	printf "%s" $bad_content_1 >$worktree1_refdir_prefix/bad-branch-1 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/worktree/bad-branch-1: badRefContent: $bad_content_1
+	EOF
+	rm $worktree1_refdir_prefix/bad-branch-1 &&
+	test_cmp expect err &&
+
+	printf "%s" $bad_content_2 >$worktree2_refdir_prefix/bad-branch-2 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/worktree/bad-branch-2: badRefContent: $bad_content_2
+	EOF
+	rm $worktree2_refdir_prefix/bad-branch-2 &&
+	test_cmp expect err &&
+
+	printf "%s" $bad_content_3 >$worktree1_refdir_prefix/bad-branch-3 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/worktree/bad-branch-3: badRefContent: $bad_content_3
+	EOF
+	rm $worktree1_refdir_prefix/bad-branch-3 &&
+	test_cmp expect err &&
+
+	printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/worktree/branch-no-newline: unofficialFormattedRef: misses LF at the end
+	EOF
+	rm $worktree1_refdir_prefix/branch-no-newline &&
+	test_cmp expect err &&
+
+	printf "%s garbage" "$(git rev-parse HEAD)" >$worktree2_refdir_prefix/branch-garbage &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	warning: refs/worktree/branch-garbage: unofficialFormattedRef: has trailing garbage: '\'' garbage'\''
+	EOF
+	rm $worktree2_refdir_prefix/branch-garbage
+'
+
 test_done
Signature · 1 line
2.46.2
#133 lore raw

Re: [PATCH v5 8/9] t0602: add ref content checks for worktrees

Quoted 3 lines · shejialuo

We have already added content tests, but we don't have tests when there are worktrees in the repository. Add a new test to test all the functionalities we have added for worktrees.

I'd squash this commit into the one where you introduced checks for worktrees. Or if this exercises errors that you have added in subsequent commits I'd squash it into the respective commit that introduces those checks.

Patrick

author
#134 lore raw

Re: [PATCH v5 8/9] t0602: add ref content checks for worktrees

Quoted 10 lines · Patrick Steinhardt
Quoted 3 lines · shejialuo

We have already added content tests, but we don't have tests when there are worktrees in the repository. Add a new test to test all the functionalities we have added for worktrees.

I'd squash this commit into the one where you introduced checks for worktrees. Or if this exercises errors that you have added in subsequent commits I'd squash it into the respective commit that introduces those checks.

Yes, make sense. I will improve this in the next version.

Quoted 1 line

Patrick

author
#135 lore raw

PATCH v5 9/9 ref: add symlink ref content check for files backend

In reply to shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

We firstly use the "strbuf_add_real_path" to resolve the symlink and get the absolute path as the "ref_content" which the symlink ref points to. Then we can use the absolute "abs_gitdir" of the "gitdir" and then combine "ref_content" and "abs_gitdir" to extract the relative path "referent". If "ref_content" is outside of "gitdir", we just use the "ref_content" as the "referent". Thus, we can reuse "files_fsck_symref_target" function to seamlessly check the symlink refs.

Because we consider deprecating writing the symbolic links. We first need to asses whether symbolic links may still be used. So, add a new fsck message "symlinkRef(INFO)" to tell the user be aware of this information.

---
 Documentation/fsck-msgids.txt |  6 +++++
 fsck.h                        |  1 +
 refs/files-backend.c          | 43 ++++++++++++++++++++++++++++-----
 t/t0602-reffiles-fsck.sh      | 45 +++++++++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+), 6 deletions(-)
Documentation/fsck-msgids.txt +6 -0
index 223974057d..ffe9d6a2f6 100644
@@ -184,6 +184,12 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`symlinkRef`::
+	(INFO) A symbolic link is used as a symref.  Report to the
+	[email protected] mailing list if you see this error, as we
+	are assessing the feasibility of dropping the support to drop
+	creating symblinks as symrefs.
+
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
 
fsck.h +1 -0
index 5ecee0fda5..f1da5c8a77 100644
@@ -87,6 +87,7 @@ enum fsck_msg_type {
 	FUNC(MAILMAP_SYMLINK, INFO) \
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
+	FUNC(SYMLINK_REF, INFO) \
 	FUNC(UNOFFICIAL_FORMATTED_REF, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
refs/files-backend.c +37 -6

91 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +45 -0

49 lines not on this page · raw message

Signature · 1 line
2.46.2
#136 lore raw

Re: [PATCH v5 9/9] ref: add symlink ref content check for files backend

Quoted 5 lines · shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

You're getting into implementation details before noting what the actual problem is. So I'd recommend first describing the problem at a higher level, and then note that we can reuse parts of preexisting infra to address the issue.

Patrick

author
#137 lore raw

Re: [PATCH v5 9/9] ref: add symlink ref content check for files backend

Quoted 7 lines · Patrick Steinhardt
Quoted 5 lines · shejialuo

We have already introduced "files_fsck_symref_target". We should reuse this function to handle the symrefs which use legacy symbolic links. We should not check the trailing garbage for symbolic refs. Add a new parameter "symbolic_link" to disable some checks which should only be executed for textual symrefs.

Quoted 5 lines

You're getting into implementation details before noting what the actual problem is. So I'd recommend first describing the problem at a higher level, and then note that we can reuse parts of preexisting infra to address the issue.

Thanks, I will improve this in the next version.

Quoted 1 line

Patrick

#138 lore raw

Re: [PATCH v5 0/9] add ref content check for files backend

In reply to shejialuo
Quoted 4 lines · shejialuo

Because I do not sync the upstream for a long time. For this series, I sync the latest upstream and generate the patch, it is based on

3857aae53f (Git 2.47-rc0, 2024-09-25)

Does this help to reduce conflicts when merging the topic to say 'next' or 'seen'? If so, such a rebase and noting it in the cover letter message, like you just did, is very much appreciated.

If not, please don't ;-).

Quoted 2 lines

And I don't think range-diff is useful, it is messy for the reviewers. Actually, there are not so many logic changes in this new version.

OK, so this needs a fresh full review. Thanks.

author
#139 lore raw

Re: [PATCH v5 0/9] add ref content check for files backend

Quoted 5 lines · Junio C Hamano
Quoted 3 lines · shejialuo

Because I do not sync the upstream for a long time. For this series, I sync the latest upstream and generate the patch, it is based on

Quoted 8 lines
Quoted 1 line

3857aae53f (Git 2.47-rc0, 2024-09-25)

Does this help to reduce conflicts when merging the topic to say 'next' or 'seen'? If so, such a rebase and noting it in the cover letter message, like you just did, is very much appreciated.

If not, please don't ;-).

Actually, I am sure that there is no conflicts after squashing the following two patches.

<[email protected]>
<[email protected]>

The reason why I just sync the upstream is that the build system (such as warning about unused parameters) and CIs are all changed.

I will remember this.

Thanks,
Jialuo

author
#140 lore raw

Re: [PATCH v5 0/9] add ref content check for files backend

In reply to shejialuo

From the discussion with Patrick in v5 and Junio in v4. I conclude the follow things:

1. "fsck_ref_report" should not be refactored to accept NULL. There would be only one situation where it will be a little bad (the content of a ref does not end with a newline). In the other situations, the message part will be useful, such as:

refs/heads/garbage-branch: trailingRefContent: ' garbage'. refs/heads/escape: escapeReferent: referent 'xxx' is outside.

Although for some messages, only use fsck message id is enough. But we could also specify the message. It's not harmful anyway.

2. The mapping from fsck message id to error case should be one to one. This is essentially important because the user could set the fsck error levels. If we use multiple to one, we will give the user a bad experience. We should avoid this.

I will wait for more comments to ensure the next version will be better.

Thanks,
Jialuo

author
#141 lore raw

v6 cover add ref content check for files backend

In reply to shejialuo

Hi All:

This new version updates the following things.

First, I want to talk about the new things. [PATCH v6 2/9] and [PATCH v6 3/9] are used to solve a bug when I implemented the checks for refname for the following code:

    if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
          ret = fsck_report(...);
    }

So, the code will wrongly report an error for "refs/heads/@". And I fix this issue by using two commits.

For the difference against the previous version

1. Split [PATCH v5 8/9] into every related commit.

2. In [PATCH v6 4/9], print the worktree ref fullname to avoid ambiguous.

3. Use one-to-one mapping fsck message.

4. Enhance the commit message and the usage of "fsck_report_ref" to provide more useful information.

5. Rename "escapeReferent" to "symrefTargetIsNotARef". I agree that we should use this. "escpae" is not right. However, I cannot find an elegant name. So I follow the advice from Patrick.

I provide the "interdiff" here which will be helpful for reviewers.

Thanks,
Jialuo

shejialuo (9):
ref: initialize "fsck_ref_report" with zero
ref: check the full refname instead of basename
ref: initialize target name outside of check functions
ref: support multiple worktrees check for refs
ref: port git-fsck(1) regular refs check for files backend
ref: add more strict checks for regular refs
ref: add basic symref content check for files backend
ref: check whether the target of the symref is a ref
ref: add symlink ref content check for files backend

 Documentation/fsck-msgids.txt |  35 +++
 builtin/refs.c                |  12 +-
 fsck.h                        |   6 +
 refs.c                        |   7 +-
 refs.h                        |   3 +-
 refs/debug.c                  |   5 +-
 refs/files-backend.c          | 187 ++++++++++++--
 refs/packed-backend.c         |   8 +-
 refs/refs-internal.h          |   5 +-
 refs/reftable-backend.c       |   3 +-
 t/t0602-reffiles-fsck.sh      | 457 +++++++++++++++++++++++++++++++++-
 11 files changed, 693 insertions(+), 35 deletions(-)

Interdiff against v5:

Documentation/fsck-msgids.txt +27 -20
index ffe9d6a2f6..b14bc44ca4 100644
@@ -28,8 +28,8 @@
 `badRefName`::
 	(ERROR) A ref has an invalid format.
 
-`badReferent`::
-	(ERROR) The referent of a ref is invalid.
+`badReferentName`::
+	(ERROR) The referent name of a symref is invalid.
 
 `badTagName`::
 	(INFO) A tag has an invalid format.
@@ -52,14 +52,6 @@
 `emptyName`::
 	(WARN) A path contains an empty name.
 
-`escapeReferent`::
-	(INFO) The referent of a symref is outside the "ref" directory.
-	Although we allow create a symref pointing to the referent which
-	is outside the "ref" by using `git symbolic-ref`, we may tighten
-	the rule in the future. Report to the [email protected]
-	mailing list if you see this error, as we need to know what tools
-	created such a file.
-
 `extraHeaderEntry`::
 	(IGNORE) Extra headers found after `tagger`.
 
@@ -184,11 +176,34 @@
 `nullSha1`::
 	(WARN) Tree contains entries pointing to a null sha1.
 
+`refMissingNewline`::
+	(INFO) A loose ref that does not end with newline(LF). As
+	valid implementations of Git never created such a loose ref
+	file, it may become an error in the future. Report to the
+	[email protected] mailing list if you see this error, as
+	we need to know what tools created such a file.
+
 `symlinkRef`::
-	(INFO) A symbolic link is used as a symref.  Report to the
+	(INFO) A symbolic link is used as a symref. Report to the
 	[email protected] mailing list if you see this error, as we
 	are assessing the feasibility of dropping the support to drop
-	creating symblinks as symrefs.
+	creating symbolic links as symrefs.
+
+`symrefTargetIsNotARef`::
+	(INFO) The target of a symbolic reference points neither to
+	a root reference nor to a reference starting with "refs/".
+	Although we allow create a symref pointing to the referent which
+	is outside the "ref" by using `git symbolic-ref`, we may tighten
+	the rule in the future. Report to the [email protected]
+	mailing list if you see this error, as we need to know what tools
+	created such a file.
+
+`trailingRefContent`::
+	(INFO) A loose ref has trailing content. As valid implementations
+	of Git never created such a loose ref file, it may become an
+	error in the future. Report to the [email protected] mailing
+	list if you see this error, as we need to know what tools
+	created such a file.
 
 `treeNotSorted`::
 	(ERROR) A tree is not properly sorted.
@@ -196,14 +211,6 @@
 `unknownType`::
 	(ERROR) Found an unknown object type.
 
-`unofficialFormattedRef`::
-	(INFO) The content of a loose ref file is not in the official
-	format such as not having a LF at the end or having trailing
-	garbage. As valid implementations of Git never created such a
-	loose ref file, it may become an error in the future. Report
-	to the [email protected] mailing list if you see this error,
-	as we need to know what tools created such a file.
-
 `unterminatedHeader`::
 	(FATAL) Missing end-of-line in the object header.
 
builtin/refs.c +2 -1
index 3c492ea922..886c4ceae3 100644
@@ -89,9 +89,10 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	worktrees = get_worktrees();
 	for (p = worktrees; *p; p++) {
 		struct worktree *wt = *p;
-		ret += refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options);
+		ret |= refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options, wt);
 	}
 
+
 	fsck_options_clear(&fsck_refs_options);
 	free_worktrees(worktrees);
 	return ret;
fsck.h +4 -3
index f1da5c8a77..a44c231a5f 100644
@@ -34,7 +34,7 @@ enum fsck_msg_type {
 	FUNC(BAD_REF_CONTENT, ERROR) \
 	FUNC(BAD_REF_FILETYPE, ERROR) \
 	FUNC(BAD_REF_NAME, ERROR) \
-	FUNC(BAD_REFERENT, ERROR) \
+	FUNC(BAD_REFERENT_NAME, ERROR) \
 	FUNC(BAD_TIMEZONE, ERROR) \
 	FUNC(BAD_TREE, ERROR) \
 	FUNC(BAD_TREE_SHA1, ERROR) \
@@ -80,7 +80,6 @@ enum fsck_msg_type {
 	FUNC(LARGE_PATHNAME, WARN) \
 	/* infos (reported as warnings, but ignored by default) */ \
 	FUNC(BAD_FILEMODE, INFO) \
-	FUNC(ESCAPE_REFERENT, INFO) \
 	FUNC(GITMODULES_PARSE, INFO) \
 	FUNC(GITIGNORE_SYMLINK, INFO) \
 	FUNC(GITATTRIBUTES_SYMLINK, INFO) \
@@ -88,7 +87,9 @@ enum fsck_msg_type {
 	FUNC(BAD_TAG_NAME, INFO) \
 	FUNC(MISSING_TAGGER_ENTRY, INFO) \
 	FUNC(SYMLINK_REF, INFO) \
-	FUNC(UNOFFICIAL_FORMATTED_REF, INFO) \
+	FUNC(REF_MISSING_NEWLINE, INFO) \
+	FUNC(SYMREF_TARGET_IS_NOT_A_REF, INFO) \
+	FUNC(TRAILING_REF_CONTENT, INFO) \
 	/* ignored (elevated when requested) */ \
 	FUNC(EXTRA_HEADER_ENTRY, IGNORE)
 
refs.c +3 -2
index 6ba1bb1aa1..f88b32a633 100644
@@ -318,9 +318,10 @@ int check_refname_format(const char *refname, int flags)
 	return check_or_sanitize_refname(refname, flags, NULL);
 }
 
-int refs_fsck(struct ref_store *refs, struct fsck_options *o)
+int refs_fsck(struct ref_store *refs, struct fsck_options *o,
+	      struct worktree *wt)
 {
-	return refs->be->fsck(refs, o);
+	return refs->be->fsck(refs, o, wt);
 }
 
 void sanitize_refname_component(const char *refname, struct strbuf *out)
refs.h +2 -1
index 108dfc93b3..341d43239c 100644
@@ -549,7 +549,8 @@ int check_refname_format(const char *refname, int flags);
  * reflogs are consistent, and non-zero otherwise. The errors will be
  * written to stderr.
  */
-int refs_fsck(struct ref_store *refs, struct fsck_options *o);
+int refs_fsck(struct ref_store *refs, struct fsck_options *o,
+	      struct worktree *wt);
 
 /*
  * Apply the rules from check_refname_format, but mutate the result until it
refs/debug.c +3 -2
index 45e2e784a0..72e80ddd6d 100644
@@ -420,10 +420,11 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
 }
 
 static int debug_fsck(struct ref_store *ref_store,
-		      struct fsck_options *o)
+		      struct fsck_options *o,
+		      struct worktree *wt)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
-	int res = drefs->refs->be->fsck(drefs->refs, o);
+	int res = drefs->refs->be->fsck(drefs->refs, o, wt);
 	trace_printf_key(&trace_refs, "fsck: %d\n", res);
 	return res;
 }
refs/files-backend.c +48 -45

235 lines not on this page · raw message

refs/packed-backend.c +7 -1
index 07c57fd541..46dcaec654 100644
@@ -13,6 +13,7 @@
 #include "../lockfile.h"
 #include "../chdir-notify.h"
 #include "../statinfo.h"
+#include "../worktree.h"
 #include "../wrapper.h"
 #include "../write-or-die.h"
 #include "../trace2.h"
@@ -1754,8 +1755,13 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
 }
 
 static int packed_fsck(struct ref_store *ref_store UNUSED,
-		       struct fsck_options *o UNUSED)
+		       struct fsck_options *o UNUSED,
+		       struct worktree *wt)
 {
+
+	if (!is_main_worktree(wt))
+		return 0;
+
 	return 0;
 }
 
refs/refs-internal.h +2 -1
index 73b05f971b..125f1fe735 100644
@@ -653,7 +653,8 @@ typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refnam
 				 struct strbuf *referent);
 
 typedef int fsck_fn(struct ref_store *ref_store,
-		    struct fsck_options *o);
+		    struct fsck_options *o,
+		    struct worktree *wt);
 
 struct ref_storage_be {
 	const char *name;
refs/reftable-backend.c +2 -1
index f5f957e6de..b6a63c1015 100644
@@ -2443,7 +2443,8 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 }
 
 static int reftable_be_fsck(struct ref_store *ref_store UNUSED,
-			    struct fsck_options *o UNUSED)
+			    struct fsck_options *o UNUSED,
+			    struct worktree *wt UNUSED)
 {
 	return 0;
 }
t/t0602-reffiles-fsck.sh +117 -116

440 lines not on this page · raw message

Signature · 1 line
2.47.0
author
#142 lore raw

PATCH v6 1/9 ref: initialize "fsck_ref_report" with zero

In "fsck.c::fsck_refs_error_function", we need to tell whether "oid" and "referent" is NULL. So, we need to always initialize these parameters to NULL instead of letting them point to anywhere when creating a new "fsck_ref_report" structure.

The original code explicitly initializes the "path" member in the "struct fsck_ref_report" to NULL (which implicitly 0-initializes other members in the struct). It is more customary to use "{ 0 }" to express that we are 0-initializing everything. In order to align with the codebase, initialize "fsck_ref_report" with zero.

---
 refs/files-backend.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
refs/files-backend.c +1 -1
index 0824c0b8a9..03d2503276 100644
@@ -3520,7 +3520,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 		goto cleanup;
 
 	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
-		struct fsck_ref_report report = { .path = NULL };
+		struct fsck_ref_report report = { 0 };
 
 		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
Signature · 1 line
2.47.0
author
#143 lore raw

PATCH v6 2/9 ref: check the full refname instead of basename

In reply to shejialuo

In "files-backend.c::files_fsck_refs_name", we validate the refname format by using "check_refname_format" to check the basename of the iterator with "REFNAME_ALLOW_ONELEVEL" flag.

However, this is a bad implementation. Although we doesn't allow a single "@" in ".git" directory, we do allow "refs/heads/@". So, we will report an error wrongly when there is a "refs/heads/@" ref by using one level refname "@".

Because we just check one level refname, we either cannot check the other parts of the full refname. And we will ignore the following errors:

"refs/heads/ new-feature/test"
"refs/heads/~new-feature/test"

In order to fix the above problem, enhance "files_fsck_refs_name" to use the full name for "check_refname_format". Then, replace the tests which are related to "@" and add tests to exercise the above situations.

---
 refs/files-backend.c     |  4 ++--
 t/t0602-reffiles-fsck.sh | 30 +++++++++++++++++++++++-------
 2 files changed, 25 insertions(+), 9 deletions(-)
refs/files-backend.c +2 -2
index 03d2503276..f246c92684 100644
@@ -3519,10 +3519,10 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 	if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
 		goto cleanup;
 
-	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
+	strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
+	if (check_refname_format(sb.buf, 0)) {
 		struct fsck_ref_report report = { 0 };
 
-		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_NAME,
t/t0602-reffiles-fsck.sh +23 -7
index 71a4d1a5ae..0aee377439 100755
@@ -25,6 +25,13 @@ test_expect_success 'ref name should be checked' '
 	git tag tag-2 &&
 	git tag multi_hierarchy/tag-2 &&
 
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	EOF
+	test_must_be_empty err &&
+	rm $branch_dir_prefix/@ &&
+
 	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
@@ -33,20 +40,20 @@ test_expect_success 'ref name should be checked' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/@: badRefName: invalid refname format
+	error: refs/heads/ branch-1: badRefName: invalid refname format
 	EOF
-	rm $branch_dir_prefix/@ &&
+	rm $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_cmp expect err &&
 
-	cp $tag_dir_prefix/multi_hierarchy/tag-2 $tag_dir_prefix/multi_hierarchy/@ &&
+	cp $tag_dir_prefix/multi_hierarchy/tag-2 $tag_dir_prefix/multi_hierarchy/'\''~tag-2'\'' &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/tags/multi_hierarchy/@: badRefName: invalid refname format
+	error: refs/tags/multi_hierarchy/~tag-2: badRefName: invalid refname format
 	EOF
-	rm $tag_dir_prefix/multi_hierarchy/@ &&
+	rm $tag_dir_prefix/multi_hierarchy/'\''~tag-2'\'' &&
 	test_cmp expect err &&
 
 	cp $tag_dir_prefix/tag-1 $tag_dir_prefix/tag-1.lock &&
@@ -60,6 +67,15 @@ test_expect_success 'ref name should be checked' '
 	error: refs/tags/.lock: badRefName: invalid refname format
 	EOF
 	rm $tag_dir_prefix/.lock &&
+	test_cmp expect err &&
+
+	mkdir $tag_dir_prefix/'\''~new-feature'\'' &&
+	cp $tag_dir_prefix/tag-1 $tag_dir_prefix/'\''~new-feature'\''/tag-1 &&
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: refs/tags/~new-feature/tag-1: badRefName: invalid refname format
+	EOF
+	rm -rf $tag_dir_prefix/'\''~new-feature'\'' &&
 	test_cmp expect err
 '
 
@@ -84,7 +100,7 @@ test_expect_success 'ref name check should be adapted into fsck messages' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\''~branch-1'\'' &&
 	git -c fsck.badRefName=ignore refs verify 2>err &&
 	test_must_be_empty err
 '
Signature · 1 line
2.47.0
#144 lore raw

Re: [PATCH v6 2/9] ref: check the full refname instead of basename

shejialuo <[email protected]> writes:

[snip]

Quoted 19 lines
t/t0602-reffiles-fsck.sh +7 -0
index 71a4d1a5ae..0aee377439 100755
@@ -25,6 +25,13 @@ test_expect_success 'ref name should be checked' '
 	git tag tag-2 &&
 	git tag multi_hierarchy/tag-2 &&
 
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	EOF
+	test_must_be_empty err &&
+	rm $branch_dir_prefix/@ &&
+
 	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 43

Quoted 5 lines
t/t0602-reffiles-fsck.sh +1 -1
@@ -33,20 +40,20 @@ test_expect_success 'ref name should be checked' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\'' branch-1'\'' &&

Nit: Here and below we could use ${SQ} instead.

[snip]

author
#145 lore raw

Re: [PATCH v6 2/9] ref: check the full refname instead of basename

Quoted 26 lines · karthik nayak

shejialuo <[email protected]> writes:

[snip]

Quoted 22 lines
t/t0602-reffiles-fsck.sh +7 -0
index 71a4d1a5ae..0aee377439 100755
@@ -25,6 +25,13 @@ test_expect_success 'ref name should be checked' '
 	git tag tag-2 &&
 	git tag multi_hierarchy/tag-2 &&
 
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	EOF
+	test_must_be_empty err &&
+	rm $branch_dir_prefix/@ &&
+
 	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
@@ -33,20 +40,20 @@ test_expect_success 'ref name should be checked' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 
Quoted 5 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 43

Quoted 2 lines
t/t0602-reffiles-fsck.sh +1 -1
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\'' branch-1'\'' &&

Nit: Here and below we could use ${SQ} instead.

I agree.

Quoted 1 line

[snip]

#146 lore raw

Re: [PATCH v6 2/9] ref: check the full refname instead of basename

In reply to shejialuo
Quoted 13 lines · shejialuo

In "files-backend.c::files_fsck_refs_name", we validate the refname format by using "check_refname_format" to check the basename of the iterator with "REFNAME_ALLOW_ONELEVEL" flag.

However, this is a bad implementation. Although we doesn't allow a single "@" in ".git" directory, we do allow "refs/heads/@". So, we will report an error wrongly when there is a "refs/heads/@" ref by using one level refname "@".

Because we just check one level refname, we either cannot check the other parts of the full refname. And we will ignore the following errors:

Quoted 6 lines

"refs/heads/ new-feature/test"
"refs/heads/~new-feature/test"

In order to fix the above problem, enhance "files_fsck_refs_name" to use the full name for "check_refname_format". Then, replace the tests which are related to "@" and add tests to exercise the above situations.

Okay, makes sense.

Quoted 7 lines
refs/files-backend.c +0 -0
index 03d2503276..f246c92684 100644
@@ -3519,10 +3519,10 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 	if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
 		goto cleanup;

refs/files-backend.c static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, · line 3528

Quoted 10 lines
refs/files-backend.c +2 -2
 
-	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
+	strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
+	if (check_refname_format(sb.buf, 0)) {
 		struct fsck_ref_report report = { 0 };
 
-		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_NAME,

So this only works right now because we never check root refs in the first place? Maybe that is worth a comment.

Quoted 5 lines
t/t0602-reffiles-fsck.sh +0 -0
index 71a4d1a5ae..0aee377439 100755

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 33

Quoted 9 lines
t/t0602-reffiles-fsck.sh +6 -0
@@ -25,6 +25,13 @@ test_expect_success 'ref name should be checked' '
 	git tag tag-2 &&
 	git tag multi_hierarchy/tag-2 &&
 
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	EOF
+	test_must_be_empty err &&
+	rm $branch_dir_prefix/@ &&

expect isn't used here as you use test_must_be_empty.

Quoted 7 lines
t/t0602-reffiles-fsck.sh +0 -0
 	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
@@ -33,20 +40,20 @@ test_expect_success 'ref name should be checked' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 49

Quoted 10 lines
t/t0602-reffiles-fsck.sh +3 -3
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/@: badRefName: invalid refname format
+	error: refs/heads/ branch-1: badRefName: invalid refname format
 	EOF
-	rm $branch_dir_prefix/@ &&
+	rm $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_cmp expect err &&

Okay, we now allow refs/heads/@, but still don't allow other bad formatting like spaces in the refname.

Patrick

author
#147 lore raw

Re: [PATCH v6 2/9] ref: check the full refname instead of basename

On Tue, Nov 05, 2024 at 08:11:42AM +0100, Patrick Steinhardt wrote:

[snip]

Quoted 13 lines
Quoted 13 lines
refs/files-backend.c +2 -1
index 03d2503276..f246c92684 100644
@@ -3519,10 +3519,10 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 	if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
 		goto cleanup;
 
-	if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
+	strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
+	if (check_refname_format(sb.buf, 0)) {
 		struct fsck_ref_report report = { 0 };
 
Quoted 8 lines

refs/files-backend.c static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, · line 3528

Quoted 4 lines
refs/files-backend.c +0 -1
-		strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
 		report.path = sb.buf;
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_NAME,

So this only works right now because we never check root refs in the first place? Maybe that is worth a comment.

Yes, I agree. I will improve this in the next version.

Quoted 8 lines
Quoted 8 lines
t/t0602-reffiles-fsck.sh +0 -0
index 71a4d1a5ae..0aee377439 100755
@@ -25,6 +25,13 @@ test_expect_success 'ref name should be checked' '
 	git tag tag-2 &&
 	git tag multi_hierarchy/tag-2 &&
 
Quoted 9 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 33

Quoted 6 lines
t/t0602-reffiles-fsck.sh +6 -0
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	EOF
+	test_must_be_empty err &&
+	rm $branch_dir_prefix/@ &&

expect isn't used here as you use test_must_be_empty.

Thanks, I will improve this in the next version.

Quoted 7 lines
Quoted 7 lines
t/t0602-reffiles-fsck.sh +0 -0
 	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
@@ -33,20 +40,20 @@ test_expect_success 'ref name should be checked' '
 	rm $branch_dir_prefix/.branch-1 &&
 	test_cmp expect err &&
 
Quoted 14 lines

t/t0602-reffiles-fsck.sh test_expect_success 'ref name should be checked' ' · line 49

Quoted 10 lines
t/t0602-reffiles-fsck.sh +3 -3
-	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+	cp $branch_dir_prefix/branch-1 $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_must_fail git refs verify 2>err &&
 	cat >expect <<-EOF &&
-	error: refs/heads/@: badRefName: invalid refname format
+	error: refs/heads/ branch-1: badRefName: invalid refname format
 	EOF
-	rm $branch_dir_prefix/@ &&
+	rm $branch_dir_prefix/'\'' branch-1'\'' &&
 	test_cmp expect err &&

Okay, we now allow refs/heads/@, but still don't allow other bad formatting like spaces in the refname.

Yes, this is a mistake. Junio have told me in this patch and I have realized this.

https://lore.kernel.org/git/[email protected]/

Quoted 1 line

Patrick

Thanks,
Jialuo

author
#148 lore raw

PATCH v6 3/9 ref: initialize target name outside of check functions

In reply to shejialuo

We passes "refs_check_dir" to the "files_fsck_refs_name" function which allows it to create the checked ref name later. However, when we introduce a new check function, we have to re-calculate the target name. It's bad for us to do repeat calculation. Instead, we should calculate it only once and pass the target name to the check functions.

In order not to do repeat calculation, rename "refs_check_dir" to "target_name". And in "files_fsck_refs_dir", create a new strbuf "target_name", thus whenever we handle a new target, calculate the name and call the check functions one by one.

---
 refs/files-backend.c | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)
refs/files-backend.c +13 -8
index f246c92684..fbfcd1115c 100644
@@ -3501,12 +3501,12 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
  */
 typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
 				  struct fsck_options *o,
-				  const char *refs_check_dir,
+				  const char *target_name,
 				  struct dir_iterator *iter);
 
 static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 				struct fsck_options *o,
-				const char *refs_check_dir,
+				const char *target_name,
 				struct dir_iterator *iter)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -3519,11 +3519,10 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 	if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
 		goto cleanup;
 
-	strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
-	if (check_refname_format(sb.buf, 0)) {
+	if (check_refname_format(target_name, 0)) {
 		struct fsck_ref_report report = { 0 };
 
-		report.path = sb.buf;
+		report.path = target_name;
 		ret = fsck_report_ref(o, &report,
 				      FSCK_MSG_BAD_REF_NAME,
 				      "invalid refname format");
@@ -3539,6 +3538,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 			       const char *refs_check_dir,
 			       files_fsck_refs_fn *fsck_refs_fn)
 {
+	struct strbuf target_name = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 	struct dir_iterator *iter;
 	int iter_status;
@@ -3557,11 +3557,15 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 			continue;
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
+			strbuf_reset(&target_name);
+			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
+				    iter->relative_path);
+
 			if (o->verbose)
-				fprintf_ln(stderr, "Checking %s/%s",
-					   refs_check_dir, iter->relative_path);
+				fprintf_ln(stderr, "Checking %s", target_name.buf);
+
 			for (size_t i = 0; fsck_refs_fn[i]; i++) {
-				if (fsck_refs_fn[i](ref_store, o, refs_check_dir, iter))
+				if (fsck_refs_fn[i](ref_store, o, target_name.buf, iter))
 					ret = -1;
 			}
 		} else {
@@ -3578,6 +3582,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 
 out:
 	strbuf_release(&sb);
+	strbuf_release(&target_name);
 	return ret;
 }
 
Signature · 1 line
2.47.0
#149 lore raw

Re: [PATCH v6 3/9] ref: initialize target name outside of check functions

Quoted 8 lines · shejialuo

We passes "refs_check_dir" to the "files_fsck_refs_name" function which allows it to create the checked ref name later. However, when we introduce a new check function, we have to re-calculate the target name. It's bad for us to do repeat calculation. Instead, we should calculate it only once and pass the target name to the check functions.

In order not to do repeat calculation, rename "refs_check_dir" to "target_name". And in "files_fsck_refs_dir", create a new strbuf

Nit: Why target_name and not simply target?

Quoted 10 lines

"target_name", thus whenever we handle a new target, calculate the name and call the check functions one by one.

---
 refs/files-backend.c | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

[snip]

#150 lore raw

Re: [PATCH v6 3/9] ref: initialize target name outside of check functions

In reply to shejialuo
Quoted 5 lines · shejialuo

We passes "refs_check_dir" to the "files_fsck_refs_name" function which allows it to create the checked ref name later. However, when we introduce a new check function, we have to re-calculate the target name. It's bad for us to do repeat calculation. Instead, we should calculate it only once and pass the target name to the check functions.

It would be nice to clarify what exactly is bad about it. Does it create extra memory churn? Or is this about not duplicating logic?

Quoted 4 lines

In order not to do repeat calculation, rename "refs_check_dir" to "target_name". And in "files_fsck_refs_dir", create a new strbuf "target_name", thus whenever we handle a new target, calculate the name and call the check functions one by one.

"target_name" is somewhat of a weird name. I'd expect that this is either the path to the reference, in which case I'd call this "path", or the name of the reference that is to be checked, in which case I'd call this "refname".

Quoted 17 lines
refs/files-backend.c +5 -0
@@ -3539,6 +3538,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 			       const char *refs_check_dir,
 			       files_fsck_refs_fn *fsck_refs_fn)
 {
+	struct strbuf target_name = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 	struct dir_iterator *iter;
 	int iter_status;
@@ -3557,11 +3557,15 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 			continue;
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
+			strbuf_reset(&target_name);
+			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
+				    iter->relative_path);
+
 			if (o->verbose)

refs/files-backend.c static int files_fsck_refs_dir(struct ref_store *ref_store, · line 3571

Quoted 10 lines
refs/files-backend.c +3 -3
-				fprintf_ln(stderr, "Checking %s/%s",
-					   refs_check_dir, iter->relative_path);
+				fprintf_ln(stderr, "Checking %s", target_name.buf);
+
 			for (size_t i = 0; fsck_refs_fn[i]; i++) {
-				if (fsck_refs_fn[i](ref_store, o, refs_check_dir, iter))
+				if (fsck_refs_fn[i](ref_store, o, target_name.buf, iter))
 					ret = -1;
 			}
 		} else {

The change itself does make sense though. We indeed avoid reallocating the array for every single ref, which is a worthwhile change.

I was wondering whether we could reuse sb here, but we do use it at the end of the function to potentially print an error message.

Patrick

author
#151 lore raw

Re: [PATCH v6 3/9] ref: initialize target name outside of check functions

Quoted 10 lines · Patrick Steinhardt
Quoted 5 lines · shejialuo

We passes "refs_check_dir" to the "files_fsck_refs_name" function which allows it to create the checked ref name later. However, when we introduce a new check function, we have to re-calculate the target name. It's bad for us to do repeat calculation. Instead, we should calculate it only once and pass the target name to the check functions.

It would be nice to clarify what exactly is bad about it. Does it create extra memory churn? Or is this about not duplicating logic?

Thanks, I will improve this in the next version.

Quoted 10 lines
Quoted 4 lines

In order not to do repeat calculation, rename "refs_check_dir" to "target_name". And in "files_fsck_refs_dir", create a new strbuf "target_name", thus whenever we handle a new target, calculate the name and call the check functions one by one.

"target_name" is somewhat of a weird name. I'd expect that this is either the path to the reference, in which case I'd call this "path", or the name of the reference that is to be checked, in which case I'd call this "refname".

I felt quite hard to name this variable when I wrote the code. "refname" is not suitable due to we may check the reflog later by calling "files_fsck_refs_dir" function.

So, we should use "path" here.

Thanks,
Jialuo

#152 lore raw

Re: [PATCH v6 3/9] ref: initialize target name outside of check functions

Quoted 13 lines · shejialuo
Quoted 11 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

In order not to do repeat calculation, rename "refs_check_dir" to "target_name". And in "files_fsck_refs_dir", create a new strbuf "target_name", thus whenever we handle a new target, calculate the name and call the check functions one by one.

"target_name" is somewhat of a weird name. I'd expect that this is either the path to the reference, in which case I'd call this "path", or the name of the reference that is to be checked, in which case I'd call this "refname".

Quoted 3 lines

I felt quite hard to name this variable when I wrote the code. "refname" is not suitable due to we may check the reflog later by calling "files_fsck_refs_dir" function.

I anticipate that we'll likely have separate infra for checking reflogs as they are both stored in a different directory and because their format is completely different compared to normal refs. So there isn't really too much of a point to plan ahead for sharing logic here, I'd think, and thus "refname" might be a better fit. If that changes in the future we can still refactor the code.

Patrick

author
#153 lore raw

PATCH v6 4/9 ref: support multiple worktrees check for refs

In reply to shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees.

Because each worktree has its own specific refs, instead of just showing the users "refs/worktree/foo", we need to display the full name such as "worktrees/<id>/refs/worktree/foo". So we should know the id of the worktree to get the full name. Add a new parameter "struct worktree *" for "refs-internal.h::fsck_fn". Then change the related functions to follow this new interface.

The "packed-refs" only exists in the main worktree, so we should only check "packed-refs" in the main worktree. Use "is_main_worktree" method to skip checking "packed-refs" in "packed_fsck" function.

Then, enhance the "files-backend.c::files_fsck_refs_dir" function to add "worktree/<id>/" prefix when we are not in the main worktree.

Last, add a new test to check the refname when there are multiple worktrees to exercise the code.

---
 builtin/refs.c           | 12 ++++++--
 refs.c                   |  5 ++--
 refs.h                   |  3 +-
 refs/debug.c             |  5 ++--
 refs/files-backend.c     | 17 ++++++++----
 refs/packed-backend.c    |  8 +++++-
 refs/refs-internal.h     |  3 +-
 refs/reftable-backend.c  |  3 +-
 t/t0602-reffiles-fsck.sh | 59 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 100 insertions(+), 15 deletions(-)
builtin/refs.c +10 -2
index 24978a7b7b..886c4ceae3 100644
@@ -5,6 +5,7 @@
 #include "parse-options.h"
 #include "refs.h"
 #include "strbuf.h"
+#include "worktree.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--dry-run]")
@@ -66,6 +67,7 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
 static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 {
 	struct fsck_options fsck_refs_options = FSCK_REFS_OPTIONS_DEFAULT;
+	struct worktree **worktrees, **p;
 	const char * const verify_usage[] = {
 		REFS_VERIFY_USAGE,
 		NULL,
@@ -75,7 +77,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "strict", &fsck_refs_options.strict, N_("enable strict checking")),
 		OPT_END(),
 	};
-	int ret;
+	int ret = 0;
 
 	argc = parse_options(argc, argv, prefix, options, verify_usage, 0);
 	if (argc)
@@ -84,9 +86,15 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	git_config(git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(the_repository);
 
-	ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
+	worktrees = get_worktrees();
+	for (p = worktrees; *p; p++) {
+		struct worktree *wt = *p;
+		ret |= refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options, wt);
+	}
+
 
 	fsck_options_clear(&fsck_refs_options);
+	free_worktrees(worktrees);
 	return ret;
 }
 
refs.c +3 -2
index 5f729ed412..395a17273c 100644
@@ -318,9 +318,10 @@ int check_refname_format(const char *refname, int flags)
 	return check_or_sanitize_refname(refname, flags, NULL);
 }
 
-int refs_fsck(struct ref_store *refs, struct fsck_options *o)
+int refs_fsck(struct ref_store *refs, struct fsck_options *o,
+	      struct worktree *wt)
 {
-	return refs->be->fsck(refs, o);
+	return refs->be->fsck(refs, o, wt);
 }
 
 void sanitize_refname_component(const char *refname, struct strbuf *out)
refs.h +2 -1
index 108dfc93b3..341d43239c 100644
@@ -549,7 +549,8 @@ int check_refname_format(const char *refname, int flags);
  * reflogs are consistent, and non-zero otherwise. The errors will be
  * written to stderr.
  */
-int refs_fsck(struct ref_store *refs, struct fsck_options *o);
+int refs_fsck(struct ref_store *refs, struct fsck_options *o,
+	      struct worktree *wt);
 
 /*
  * Apply the rules from check_refname_format, but mutate the result until it
refs/debug.c +3 -2
index 45e2e784a0..72e80ddd6d 100644
@@ -420,10 +420,11 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
 }
 
 static int debug_fsck(struct ref_store *ref_store,
-		      struct fsck_options *o)
+		      struct fsck_options *o,
+		      struct worktree *wt)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
-	int res = drefs->refs->be->fsck(drefs->refs, o);
+	int res = drefs->refs->be->fsck(drefs->refs, o, wt);
 	trace_printf_key(&trace_refs, "fsck: %d\n", res);
 	return res;
 }
refs/files-backend.c +12 -5
index fbfcd1115c..24ad73faba 100644
@@ -23,6 +23,7 @@
 #include "../dir.h"
 #include "../chdir-notify.h"
 #include "../setup.h"
+#include "../worktree.h"
 #include "../wrapper.h"
 #include "../write-or-die.h"
 #include "../revision.h"
@@ -3536,6 +3537,7 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
 static int files_fsck_refs_dir(struct ref_store *ref_store,
 			       struct fsck_options *o,
 			       const char *refs_check_dir,
+			       struct worktree *wt,
 			       files_fsck_refs_fn *fsck_refs_fn)
 {
 	struct strbuf target_name = STRBUF_INIT;
@@ -3558,6 +3560,9 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
 			strbuf_reset(&target_name);
+
+			if (!is_main_worktree(wt))
+				strbuf_addf(&target_name, "worktrees/%s/", wt->id);
 			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
 				    iter->relative_path);
 
@@ -3587,7 +3592,8 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 }
 
 static int files_fsck_refs(struct ref_store *ref_store,
-			   struct fsck_options *o)
+			   struct fsck_options *o,
+			   struct worktree *wt)
 {
 	files_fsck_refs_fn fsck_refs_fn[]= {
 		files_fsck_refs_name,
@@ -3596,17 +3602,18 @@ static int files_fsck_refs(struct ref_store *ref_store,
 
 	if (o->verbose)
 		fprintf_ln(stderr, _("Checking references consistency"));
-	return files_fsck_refs_dir(ref_store, o,  "refs", fsck_refs_fn);
+	return files_fsck_refs_dir(ref_store, o, "refs", wt, fsck_refs_fn);
 }
 
 static int files_fsck(struct ref_store *ref_store,
-		      struct fsck_options *o)
+		      struct fsck_options *o,
+		      struct worktree *wt)
 {
 	struct files_ref_store *refs =
 		files_downcast(ref_store, REF_STORE_READ, "fsck");
 
-	return files_fsck_refs(ref_store, o) |
-	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
+	return files_fsck_refs(ref_store, o, wt) |
+	       refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt);
 }
 
 struct ref_storage_be refs_be_files = {
refs/packed-backend.c +7 -1
index 07c57fd541..46dcaec654 100644
@@ -13,6 +13,7 @@
 #include "../lockfile.h"
 #include "../chdir-notify.h"
 #include "../statinfo.h"
+#include "../worktree.h"
 #include "../wrapper.h"
 #include "../write-or-die.h"
 #include "../trace2.h"
@@ -1754,8 +1755,13 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
 }
 
 static int packed_fsck(struct ref_store *ref_store UNUSED,
-		       struct fsck_options *o UNUSED)
+		       struct fsck_options *o UNUSED,
+		       struct worktree *wt)
 {
+
+	if (!is_main_worktree(wt))
+		return 0;
+
 	return 0;
 }
 
refs/refs-internal.h +2 -1
index 2313c830d8..037d7991cd 100644
@@ -653,7 +653,8 @@ typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refnam
 				 struct strbuf *referent);
 
 typedef int fsck_fn(struct ref_store *ref_store,
-		    struct fsck_options *o);
+		    struct fsck_options *o,
+		    struct worktree *wt);
 
 struct ref_storage_be {
 	const char *name;
refs/reftable-backend.c +2 -1
index f5f957e6de..b6a63c1015 100644
@@ -2443,7 +2443,8 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
 }
 
 static int reftable_be_fsck(struct ref_store *ref_store UNUSED,
-			    struct fsck_options *o UNUSED)
+			    struct fsck_options *o UNUSED,
+			    struct worktree *wt UNUSED)
 {
 	return 0;
 }
t/t0602-reffiles-fsck.sh +59 -0

63 lines not on this page · raw message

Signature · 1 line
2.47.0
#154 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

shejialuo <[email protected]> writes:

[snip]

Quoted 55 lines
t/t0602-reffiles-fsck.sh +47 -0
index 0aee377439..6eb1385c50 100755
@@ -105,4 +105,63 @@ test_expect_success 'ref name check should be adapted into fsck messages' '
 	test_must_be_empty err
 '
 
+test_expect_success 'ref name check should work for multiple worktrees' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+
+	cd repo &&
+	test_commit initial &&
+	git checkout -b branch-1 &&
+	test_commit second &&
+	git checkout -b branch-2 &&
+	test_commit third &&
+	git checkout -b branch-3 &&
+	git worktree add ./worktree-1 branch-1 &&
+	git worktree add ./worktree-2 branch-2 &&
+	worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+	worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+	(
+		cd worktree-1 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-3
+	) &&
+	(
+		cd worktree-2 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-3
+	) &&
+
+	cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+	cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+	error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+	EOF
+	sort err >sorted_err &&
+	test_cmp expect sorted_err &&
+
+	(
+		cd worktree-1 &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+		error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+		EOF
+		sort err >sorted_err &&
+		test_cmp expect sorted_err
+	) &&
+

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 164

Quoted 10 lines
t/t0602-reffiles-fsck.sh +10 -0
+	(
+		cd worktree-2 &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+		error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+		EOF
+		sort err >sorted_err &&
+		test_cmp expect sorted_err
+	)

These last three loops are the same, couldn't we loop?

for dir in "." "worktree-1" "worktree-2"
do

    ...

done

Quoted 5 lines
t/t0602-reffiles-fsck.sh +2 -1
+'
+
 test_done
--

2.47.0

author
#155 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

Quoted 72 lines · karthik nayak

shejialuo <[email protected]> writes:

[snip]

Quoted 55 lines
t/t0602-reffiles-fsck.sh +47 -0
index 0aee377439..6eb1385c50 100755
@@ -105,4 +105,63 @@ test_expect_success 'ref name check should be adapted into fsck messages' '
 	test_must_be_empty err
 '
 
+test_expect_success 'ref name check should work for multiple worktrees' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+
+	cd repo &&
+	test_commit initial &&
+	git checkout -b branch-1 &&
+	test_commit second &&
+	git checkout -b branch-2 &&
+	test_commit third &&
+	git checkout -b branch-3 &&
+	git worktree add ./worktree-1 branch-1 &&
+	git worktree add ./worktree-2 branch-2 &&
+	worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree &&
+	worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree &&
+
+	(
+		cd worktree-1 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-3
+	) &&
+	(
+		cd worktree-2 &&
+		git update-ref refs/worktree/branch-4 refs/heads/branch-3
+	) &&
+
+	cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' &&
+	cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' &&
+
+	test_must_fail git refs verify 2>err &&
+	cat >expect <<-EOF &&
+	error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+	error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+	EOF
+	sort err >sorted_err &&
+	test_cmp expect sorted_err &&
+
+	(
+		cd worktree-1 &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+		error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+		EOF
+		sort err >sorted_err &&
+		test_cmp expect sorted_err
+	) &&
+

t/t0602-reffiles-fsck.sh test_expect_success 'ref name check should be adapted into fsck messages' ' · line 164

Quoted 10 lines
t/t0602-reffiles-fsck.sh +10 -0
+	(
+		cd worktree-2 &&
+		test_must_fail git refs verify 2>err &&
+		cat >expect <<-EOF &&
+		error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format
+		error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format
+		EOF
+		sort err >sorted_err &&
+		test_cmp expect sorted_err
+	)

These last three loops are the same, couldn't we loop?

Quoted 5 lines

for dir in "." "worktree-1" "worktree-2"
do

    ...

done

Actually, I guess all the tests could be written with that way. I need to refactor in the next version to make the tests cleaner.

Thanks,
Jialuo

#156 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

In reply to shejialuo
Quoted 4 lines · shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees.

I don't quite follow that logic: the fact that we perform more checks for the ref content doesn't necessarily mean that we also have to check worktree refs. We rather want to do that so that we get feature parity with git-fsck(1) eventually, don't we?

builtin/refs.c static int cmd_refs_migrate(int argc, const char **argv, const char *prefix) · line 73

Quoted 8 lines
builtin/refs.c +1 -0
@@ -66,6 +67,7 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
 static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 {
 	struct fsck_options fsck_refs_options = FSCK_REFS_OPTIONS_DEFAULT;
+	struct worktree **worktrees, **p;
 	const char * const verify_usage[] = {
 		REFS_VERIFY_USAGE,
 		NULL,

Instead of declaring the **p variable we can instead...

builtin/refs.c static int cmd_refs_verify(int argc, const char **argv, const char *prefix) · line 94

Quoted 11 lines
builtin/refs.c +6 -1
@@ -84,9 +86,15 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
 	git_config(git_fsck_config, &fsck_refs_options);
 	prepare_repo_settings(the_repository);
 
-	ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
+	worktrees = get_worktrees();
+	for (p = worktrees; *p; p++) {
+		struct worktree *wt = *p;
+		ret |= refs_fsck(get_worktree_ref_store(wt), &fsck_refs_options, wt);
+	}
+

... refactor this loop like this:

    for (size_t i = 0; worktrees[i]; i++)
        ret |= refs_fsck(get_worktree_ref_store(worktrees[i]),
                         &fsck_refs_options, worktrees[i]);

I was briefly wondering whether we also get worktrees in case the repo is bare, as we don't actually have a proper worktree there. But the answer seems to be "yes".

refs/files-backend.c static int files_fsck_refs_dir(struct ref_store *ref_store, · line 3568

Quoted 10 lines
refs/files-backend.c +3 -0
@@ -3558,6 +3560,9 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
 			strbuf_reset(&target_name);
+
+			if (!is_main_worktree(wt))
+				strbuf_addf(&target_name, "worktrees/%s/", wt->id);
 			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
 				    iter->relative_path);
 

Hm. Isn't it somewhat duplicate to pass both the prepended target name _and_ the worktree to the callback? I imagine that we'd have to eventually strip the worktree prefix to find the correct ref, unless we end up using the main ref store to look up the ref.

Quoted 16 lines
refs/packed-backend.c +1 -0
index 07c57fd541..46dcaec654 100644
@@ -13,6 +13,7 @@
 #include "../lockfile.h"
 #include "../chdir-notify.h"
 #include "../statinfo.h"
+#include "../worktree.h"
 #include "../wrapper.h"
 #include "../write-or-die.h"
 #include "../trace2.h"
@@ -1754,8 +1755,13 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
 }
 
 static int packed_fsck(struct ref_store *ref_store UNUSED,

refs/packed-backend.c static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s · line 1766

Quoted 10 lines
refs/packed-backend.c +6 -1
-		       struct fsck_options *o UNUSED)
+		       struct fsck_options *o UNUSED,
+		       struct worktree *wt)
 {
+
+	if (!is_main_worktree(wt))
+		return 0;
+
 	return 0;
 }

It's somewhat funny to have this condition here, but it does make sense overall as worktrees never have packed refs in the first place.

Patrick

author
#157 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

Quoted 11 lines · Patrick Steinhardt
Quoted 4 lines · shejialuo

We have already set up the infrastructure to check the consistency for refs, but we do not support multiple worktrees. As we decide to add more checks for ref content, we need to set up support for multiple worktrees.

I don't quite follow that logic: the fact that we perform more checks for the ref content doesn't necessarily mean that we also have to check worktree refs. We rather want to do that so that we get feature parity with git-fsck(1) eventually, don't we?

Yes, I agree. I come across why I wrote such message. Actually, in the very early implementation, I didn't consider about worktree situation for the "escape". And I thought I should add support for worktree. So, I made a mistake.

[snip]

Quoted 7 lines
Quoted 7 lines
refs/files-backend.c +3 -0
@@ -3558,6 +3560,9 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
 			strbuf_reset(&target_name);
+
+			if (!is_main_worktree(wt))
+				strbuf_addf(&target_name, "worktrees/%s/", wt->id);
Quoted 9 lines

refs/files-backend.c static int files_fsck_refs_dir(struct ref_store *ref_store, · line 3568

Quoted 3 lines
refs/files-backend.c +0 -0
 			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
 				    iter->relative_path);
 

Hm. Isn't it somewhat duplicate to pass both the prepended target name _and_ the worktree to the callback? I imagine that we'd have to eventually strip the worktree prefix to find the correct ref, unless we end up using the main ref store to look up the ref.

Actually, the worktree won't be passed to the callback. The fsck_refs_fn function will never use worktree wt. The reason why I use wt is that we need to print _full_ path information to the user when error happens for the situation where worktree A and worktree B has the same ref name "refs/worktree/foo".

I agree that we will strip the worktree prefix to find the correct ref in the file system. This is done by the following statement:

	strbuf_addf(&sb, "%s/%s", ref_store->gitdir, refs_check_dir);

For worktree, ref_store->gitdir will automatically be .git/worktrees/<id>.

In the v5, I didn't print the full path and we even didn't need the parameter wt. However, if we want to print the following info:

worktrees/<id>/refs/worktree/a

So, just because we need the worktrees/<id> information. Actually, we could also get the information by using "ref_store->gitdir" and "ref_store->repo->gitdir". However, this is cumbersome and it's a bad idea. So I change the prototype of "fsck_fn" to add a new parameter "struct worktree *".

Quoted 15 lines
Quoted 15 lines
refs/packed-backend.c +1 -0
index 07c57fd541..46dcaec654 100644
@@ -13,6 +13,7 @@
 #include "../lockfile.h"
 #include "../chdir-notify.h"
 #include "../statinfo.h"
+#include "../worktree.h"
 #include "../wrapper.h"
 #include "../write-or-die.h"
 #include "../trace2.h"
@@ -1754,8 +1755,13 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
 }
 
Quoted 15 lines

refs/packed-backend.c static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s · line 1766

Quoted 11 lines
refs/packed-backend.c +6 -1
 static int packed_fsck(struct ref_store *ref_store UNUSED,
-		       struct fsck_options *o UNUSED)
+		       struct fsck_options *o UNUSED,
+		       struct worktree *wt)
 {
+
+	if (!is_main_worktree(wt))
+		return 0;
+
 	return 0;
 }

It's somewhat funny to have this condition here, but it does make sense overall as worktrees never have packed refs in the first place.

Yes, there is no packed-refs in the worktree. And we need to prevent calling multiple times.

Quoted 1 line

Patrick

Thanks,
Jialuo

#158 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

Quoted 36 lines · shejialuo
Quoted 17 lines · Patrick Steinhardt

refs/files-backend.c static int files_fsck_refs_dir(struct ref_store *ref_store, · line 3568

Quoted 10 lines · shejialuo
refs/files-backend.c +3 -0
@@ -3558,6 +3560,9 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
 		} else if (S_ISREG(iter->st.st_mode) ||
 			   S_ISLNK(iter->st.st_mode)) {
 			strbuf_reset(&target_name);
+
+			if (!is_main_worktree(wt))
+				strbuf_addf(&target_name, "worktrees/%s/", wt->id);
 			strbuf_addf(&target_name, "%s/%s", refs_check_dir,
 				    iter->relative_path);
 

Hm. Isn't it somewhat duplicate to pass both the prepended target name _and_ the worktree to the callback? I imagine that we'd have to eventually strip the worktree prefix to find the correct ref, unless we end up using the main ref store to look up the ref.

Actually, the worktree won't be passed to the callback. The fsck_refs_fn function will never use worktree wt. The reason why I use wt is that we need to print _full_ path information to the user when error happens for the situation where worktree A and worktree B has the same ref name "refs/worktree/foo".

I agree that we will strip the worktree prefix to find the correct ref in the file system. This is done by the following statement:

	strbuf_addf(&sb, "%s/%s", ref_store->gitdir, refs_check_dir);

For worktree, ref_store->gitdir will automatically be .git/worktrees/<id>.

In the v5, I didn't print the full path and we even didn't need the parameter wt. However, if we want to print the following info:

Quoted 7 lines

worktrees/<id>/refs/worktree/a

So, just because we need the worktrees/<id> information. Actually, we could also get the information by using "ref_store->gitdir" and "ref_store->repo->gitdir". However, this is cumbersome and it's a bad idea. So I change the prototype of "fsck_fn" to add a new parameter "struct worktree *".

In practice you can also derive that full refname from the worktree itself, as the ID is stored in struct worktree::id. Would that maybe be a better solution?

Patrick

author
#159 lore raw

Re: [PATCH v6 4/9] ref: support multiple worktrees check for refs

On Wed, Nov 06, 2024 at 07:34:08AM +0100, Patrick Steinhardt wrote:

[snip]

Quoted 5 lines

In practice you can also derive that full refname from the worktree itself, as the ID is stored in struct worktree::id. Would that maybe be a better solution?

I think we are on the same boat. This is exactly what I have done in this patch.

Quoted 1 line

Patrick

Thanks,
Jialuo

author
#160 lore raw

PATCH v6 5/9 ref: port git-fsck(1) regular refs check for files backend

In reply to shejialuo

"git-fsck(1)" implicitly checks the ref content by passing the callback "fsck_handle_ref" to the "refs.c::refs_for_each_rawref". Then, it will check whether the ref content (eventually "oid") is valid. If not, it will report the following error to the user.

error: refs/heads/main: invalid sha1 pointer 0000...

And it will also report above errors when there are dangling symrefs in the repository wrongly. This does not align with the behavior of the "git symbolic-ref" command which allows users to create dangling symrefs.

As we have already introduced the "git refs verify" command, we'd better check the ref content explicitly in the "git refs verify" command thus later we could remove these checks in "git-fsck(1)" and launch a subprocess to call "git refs verify" in "git-fsck(1)" to make the "git-fsck(1)" more clean.

Following what "git-fsck(1)" does, 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.

---
 Documentation/fsck-msgids.txt |   3 +
 fsck.h                        |   1 +
 refs/files-backend.c          |  43 +++++++++++++
 t/t0602-reffiles-fsck.sh      | 117 ++++++++++++++++++++++++++++++++++
 4 files changed, 164 insertions(+)
Documentation/fsck-msgids.txt +3 -0
index 68a2801f15..22c385ea22 100644
@@ -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.
 
fsck.h +1 -0
index 500b4c04d2..0d99a87911 100644
@@ -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) \
refs/files-backend.c +43 -0

55 lines not on this page · raw message

t/t0602-reffiles-fsck.sh +117 -0

121 lines not on this page · raw message

Signature · 1 line
2.47.0