Skip to contentSkip to conversation

refs: return conflict error when checking packed refs

PATCH Superseded Ivan Tse via GitGitGadget 5 participants 11 messages Apr 30, 2024 – May 8, 2024

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 refs: return conflict error when checking packed refs

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

---
refs: return conflict error when checking packed refs

I wanted to provide extra context around the error message I'm referring to so it's clear what I'm trying to achieve.

First, let's get into a directory/file conflict situation:

git branch dir_file_conflict
git push origin dir_file_conflict
git update-ref -d refs/remotes/origin/dir_file_conflict git update-ref -d refs/heads/dir_file_conflict
git update-ref refs/remotes/origin/dir_file_conflict/file master

From above, the remote origin has a dir_file_conflict ref name and the local repository has a dir_file_conflict/file ref name for the remote. This situation can occur if dir_file_conflict/file used to exist and but was later deleted in the remote.

Then when we git fetch:

yolo10[master] $ git fetch
error: cannot lock ref 'refs/remotes/origin/dir_file_conflict': 'refs/remotes/origin/dir_file_conflict/file' exists; cannot create 'refs/remotes/origin/dir_file_conflict' From github.com:ivantsepp/yolo10
! [new branch] dir_file_conflict -> origin/dir_file_conflict (unable to update local ref)
error: some local refs could not be updated; try running
'git remote prune origin' to remove any old, conflicting branches

We get the helpful error message to run git remote prune origin to remove old, conflicting branches.

However, when the ref is stored in packed refs:

yolo10[master] $ git pack-refs --all
yolo10[master] $ git fetch
error: cannot lock ref 'refs/remotes/origin/dir_file_conflict': 'refs/remotes/origin/dir_file_conflict/file' exists; cannot create 'refs/remotes/origin/dir_file_conflict' From github.com:ivantsepp/yolo10
! [new branch] dir_file_conflict -> origin/dir_file_conflict (unable to update local ref)

The helpful message is not there! I believe this error message should show up regardless of how the ref is stored (loose vs packed-refs). I attempted to track down the necessary change to make this happen and it seems like a straightforward change. I hope I didn't overlook anything!

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1716%2Fivantsepp%2Freturn_name_conflict_error_for_packed_refs-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1716/ivantsepp/return_name_conflict_error_for_packed_refs-v1
Pull-Request: https://github.com/git/git/pull/1716

 refs/files-backend.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
 		 */
 		if (refs_verify_refname_available(
 				    refs->packed_ref_store, refname,
-				    extras, NULL, err))
+				    extras, NULL, err)) {
+			ret = TRANSACTION_NAME_CONFLICT;
 			goto error_return;
+		}
 	}
 
 	ret = 0;

base-commit: 786a3e4b8d754d2b14b1208b98eeb0a554ef19a8

Signature · 1 line
gitgitgadget
#1 lore raw
Quoted 6 lines · Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

Quoted 8 lines

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

The change itself makes sense to me. But I'd like to see a test that demonstrates the new behaviour so that we don't regress this in the future.

Thanks!

Patrick

#2 lore raw

PATCH v2 1/1 refs: return conflict error when checking packed refs

In reply to Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

---
refs: return conflict error when checking packed refs

Changes against v1:

* added test for the error message during git fetch

Thanks for reviewing! I've gone ahead and attempted to add tests for this behavior. It tests that the error message is shown for both cases when the ref is stored as loose vs packed-refs. How does this test look? Also, should this test have a REFFILES prerequisite?

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1716%2Fivantsepp%2Freturn_name_conflict_error_for_packed_refs-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1716/ivantsepp/return_name_conflict_error_for_packed_refs-v2
Pull-Request: https://github.com/git/git/pull/1716

Range-diff vs v1:

1: a87ba267e44 ! 1: 58b2cda5c18 refs: return conflict error when checking packed refs

     @@ refs/files-backend.c: static int lock_raw_ref(struct files_ref_store *refs,
       	}
       
       	ret = 0;
     +
     + ## t/t5510-fetch.sh ##
     +@@ t/t5510-fetch.sh: test_expect_success 'branchname D/F conflict resolved by --prune' '
     + 	test_cmp expect actual
     + '
     + 
     ++test_expect_success 'branchname D/F conflict rejected with targeted error message' '
     ++	git clone . df-conflict-error &&
     ++	git branch dir_conflict &&
     ++	(
     ++		cd df-conflict-error &&
     ++		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
     ++		test_must_fail git fetch 2>../err &&
     ++		git pack-refs --all &&
     ++		test_must_fail git fetch 2>../err-packed
     ++	) &&
     ++	test_grep "error: some local refs could not be updated; try running" err &&
     ++	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err &&
     ++	test_grep "error: some local refs could not be updated; try running" err-packed &&
     ++	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err-packed
     ++'
     ++
     + test_expect_success 'fetching a one-level ref works' '
     + 	test_commit extra &&
     + 	git reset --hard HEAD^ &&
 refs/files-backend.c |  4 +++-
 t/t5510-fetch.sh     | 16 ++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
 		 */
 		if (refs_verify_refname_available(
 				    refs->packed_ref_store, refname,
-				    extras, NULL, err))
+				    extras, NULL, err)) {
+			ret = TRANSACTION_NAME_CONFLICT;
 			goto error_return;
+		}
 	}
 
 	ret = 0;
t/t5510-fetch.sh +16 -0
index 33d34d5ae9e..ae0828e26a1 100755
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
 	test_cmp expect actual
 '
 
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+	git clone . df-conflict-error &&
+	git branch dir_conflict &&
+	(
+		cd df-conflict-error &&
+		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+		test_must_fail git fetch 2>../err &&
+		git pack-refs --all &&
+		test_must_fail git fetch 2>../err-packed
+	) &&
+	test_grep "error: some local refs could not be updated; try running" err &&
+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err &&
+	test_grep "error: some local refs could not be updated; try running" err-packed &&
+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err-packed
+'
+
 test_expect_success 'fetching a one-level ref works' '
 	test_commit extra &&
 	git reset --hard HEAD^ &&

base-commit: d4cc1ec35f3bcce816b69986ca41943f6ce21377

Signature · 1 line
gitgitgadget
#3 lore raw
Quoted 19 lines · Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

---
refs: return conflict error when checking packed refs

Quoted 8 lines

Changes against v1:

* added test for the error message during git fetch

Thanks for reviewing! I've gone ahead and attempted to add tests for this behavior. It tests that the error message is shown for both cases when the ref is stored as loose vs packed-refs. How does this test look? Also, should this test have a REFFILES prerequisite?

There is no need for the REFFILES prerequisite as you never access refs on disk directly, but instead use our plumbing commands. Furthermore, we do want to verify that both backends behave the same here. If the test failed with the "reftable" backend I'd consider that to be a bug in the backend.

[snip]

Quoted 12 lines
t/t5510-fetch.sh +4 -0
index 33d34d5ae9e..ae0828e26a1 100755
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
 	test_cmp expect actual
 '
 
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+	git clone . df-conflict-error &&
+	git branch dir_conflict &&
+	(

t/t5510-fetch.sh test_expect_success 'branchname D/F conflict resolved by --prune' ' · line 1107

Quoted 10 lines
t/t5510-fetch.sh +10 -0
+		cd df-conflict-error &&
+		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+		test_must_fail git fetch 2>../err &&
+		git pack-refs --all &&
+		test_must_fail git fetch 2>../err-packed
+	) &&
+	test_grep "error: some local refs could not be updated; try running" err &&
+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err &&
+	test_grep "error: some local refs could not be updated; try running" err-packed &&
+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err-packed

I would personally add those calls to test_grep right after the respective fetches to make the test a bit easier to follow.

Also, instead of using '\''`, you can use the "${SQ}" variable to insert single quotes. So, something like this:

    test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&

Other than that this patch looks good to me, thanks!

Patrick

Quoted 10 lines
t/t5510-fetch.sh +2 -0
+'
+
 test_expect_success 'fetching a one-level ref works' '
 	test_commit extra &&
 	git reset --hard HEAD^ &&

base-commit: d4cc1ec35f3bcce816b69986ca41943f6ce21377

Signature · 1 line
gitgitgadget
#4 lore raw

PATCH v3 1/1 refs: return conflict error when checking packed refs

In reply to Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

---
refs: return conflict error when checking packed refs

Changes against v2:

* move test_grep to follow after their respective git fetch commands
* Use "${SQ}" instead of '\''

Thanks for the explanation and for the comments. I've updated the changes to incorporate your feedback!

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1716%2Fivantsepp%2Freturn_name_conflict_error_for_packed_refs-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1716/ivantsepp/return_name_conflict_error_for_packed_refs-v3
Pull-Request: https://github.com/git/git/pull/1716

Range-diff vs v2:

1: 58b2cda5c18 ! 1: 943a8629b5f refs: return conflict error when checking packed refs

     @@ t/t5510-fetch.sh: test_expect_success 'branchname D/F conflict resolved by --pru
      +	(
      +		cd df-conflict-error &&
      +		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
     -+		test_must_fail git fetch 2>../err &&
     ++		test_must_fail git fetch 2>err &&
     ++		test_grep "error: some local refs could not be updated; try running" err &&
     ++		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&
      +		git pack-refs --all &&
     -+		test_must_fail git fetch 2>../err-packed
     -+	) &&
     -+	test_grep "error: some local refs could not be updated; try running" err &&
     -+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err &&
     -+	test_grep "error: some local refs could not be updated; try running" err-packed &&
     -+	test_grep " '\''git remote prune origin'\'' to remove any old, conflicting branches" err-packed
     ++		test_must_fail git fetch 2>err-packed &&
     ++		test_grep "error: some local refs could not be updated; try running" err-packed &&
     ++		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed
     ++	)
      +'
      +
       test_expect_success 'fetching a one-level ref works' '
 refs/files-backend.c |  4 +++-
 t/t5510-fetch.sh     | 16 ++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
 		 */
 		if (refs_verify_refname_available(
 				    refs->packed_ref_store, refname,
-				    extras, NULL, err))
+				    extras, NULL, err)) {
+			ret = TRANSACTION_NAME_CONFLICT;
 			goto error_return;
+		}
 	}
 
 	ret = 0;
t/t5510-fetch.sh +16 -0
index 33d34d5ae9e..530369266fd 100755
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
 	test_cmp expect actual
 '
 
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+	git clone . df-conflict-error &&
+	git branch dir_conflict &&
+	(
+		cd df-conflict-error &&
+		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+		test_must_fail git fetch 2>err &&
+		test_grep "error: some local refs could not be updated; try running" err &&
+		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&
+		git pack-refs --all &&
+		test_must_fail git fetch 2>err-packed &&
+		test_grep "error: some local refs could not be updated; try running" err-packed &&
+		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed
+	)
+'
+
 test_expect_success 'fetching a one-level ref works' '
 	test_commit extra &&
 	git reset --hard HEAD^ &&

base-commit: d4cc1ec35f3bcce816b69986ca41943f6ce21377

Signature · 1 line
gitgitgadget
#5 lore raw
Quoted 11 lines · Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

The TRANSACTION_NAME_CONFLICT error code refers to a failure to create a ref due to a name conflict with another ref. An example of this is a directory/file conflict such as ref names A/B and A.

"git fetch" uses this error code to more accurately describe the error by recommending to the user that they try running "git remote prune" to remove any old refs that are deleted by the remote which would clear up any directory/file conflicts.

Quoted 5 lines

This helpful error message is not displayed when the conflicted ref is stored in packed refs. This change fixes this by ensuring error return code consistency in lock_raw_ref.

This version looks good to me, thanks!

Patrick

#6 lore raw
In reply to Ivan Tse via GitGitGadget

Hello Ivan,

Quoted 1 line · Ivan Tse via GitGitGadget

From: Ivan Tse <[email protected]>

[snip]

Quoted 7 lines
refs/files-backend.c +0 -0
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
 		 */

refs/files-backend.c static int lock_raw_ref(struct files_ref_store *refs, · line 803

Quoted 10 lines
refs/files-backend.c +3 -1
 		if (refs_verify_refname_available(
 				    refs->packed_ref_store, refname,
-				    extras, NULL, err))
+				    extras, NULL, err)) {
+			ret = TRANSACTION_NAME_CONFLICT;
 			goto error_return;
+		}
 	}
 
 	ret = 0;

Shouldn't we also do this change in lock_ref_oid_basic where we gather the same lock again for creating the reflog entry?

Quoted 31 lines
t/t5510-fetch.sh +16 -0
index 33d34d5ae9e..530369266fd 100755
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
 	test_cmp expect actual
 '
 
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+	git clone . df-conflict-error &&
+	git branch dir_conflict &&
+	(
+		cd df-conflict-error &&
+		git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+		test_must_fail git fetch 2>err &&
+		test_grep "error: some local refs could not be updated; try running" err &&
+		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&
+		git pack-refs --all &&
+		test_must_fail git fetch 2>err-packed &&
+		test_grep "error: some local refs could not be updated; try running" err-packed &&
+		test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed
+	)
+'
+
 test_expect_success 'fetching a one-level ref works' '
 	test_commit extra &&
 	git reset --hard HEAD^ &&

base-commit: d4cc1ec35f3bcce816b69986ca41943f6ce21377 --
gitgitgadget

#7 lore raw
Quoted 15 lines · Karthik Nayak
Quoted 15 lines
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
 		 */
 		if (refs_verify_refname_available(
 				    refs->packed_ref_store, refname,
-				    extras, NULL, err))
+				    extras, NULL, err)) {
+			ret = TRANSACTION_NAME_CONFLICT;
 			goto error_return;
+		}
 	}
 
Quoted 5 lines

refs/files-backend.c static int lock_raw_ref(struct files_ref_store *refs, · line 803

Quoted 2 lines
refs/files-backend.c +0 -0
 	ret = 0;

Shouldn't we also do this change in lock_ref_oid_basic where we gather the same lock again for creating the reflog entry?

An interesting question.

#8 lore raw
Quoted 15 lines · Junio C Hamano
Quoted 15 lines
Quoted 15 lines
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
              */
             if (refs_verify_refname_available(
                                 refs->packed_ref_store, refname,
-                                extras, NULL, err))
+                                extras, NULL, err)) {
+                    ret = TRANSACTION_NAME_CONFLICT;
                     goto error_return;
+            }
     }
 
Quoted 7 lines
Quoted 5 lines

refs/files-backend.c static int lock_raw_ref(struct files_ref_store *refs, · line 803

Quoted 2 lines
refs/files-backend.c +0 -0
     ret = 0;

Shouldn't we also do this change in lock_ref_oid_basic where we gather the same lock again for creating the reflog entry?

An interesting question.

Hi!

Apologies but I'm not sure I follow - could you elaborate? I am not too familiar with the Git source code (or C language) but from looking at lock_ref_oid_basic, it looks like that function returns a lock struct and not an integer success/error code. I'm not sure how we would apply this change in that function as well?

#9 lore raw

Hello Ivan,

Quoted 24 lines · Ivan Tse
Quoted 15 lines · Junio C Hamano
Quoted 15 lines
Quoted 15 lines
refs/files-backend.c +3 -1
index a098d14ea00..97473f377d1 100644
@@ -794,8 +794,10 @@ static int lock_raw_ref(struct files_ref_store *refs,
              */
             if (refs_verify_refname_available(
                                 refs->packed_ref_store, refname,
-                                extras, NULL, err))
+                                extras, NULL, err)) {
+                    ret = TRANSACTION_NAME_CONFLICT;
                     goto error_return;
+            }
     }
 
Quoted 7 lines
Quoted 5 lines

refs/files-backend.c static int lock_raw_ref(struct files_ref_store *refs, · line 803

Quoted 2 lines
refs/files-backend.c +0 -0
     ret = 0;

Shouldn't we also do this change in lock_ref_oid_basic where we gather the same lock again for creating the reflog entry?

An interesting question.

Quoted 7 lines

Hi!

Apologies but I'm not sure I follow - could you elaborate? I am not too familiar with the Git source code (or C language) but from looking at lock_ref_oid_basic, it looks like that function returns a lock struct and not an integer success/error code. I'm not sure how we would apply this change in that function as well?

That's correct, my question was merely that we also have a refs_verify_refname_available function call there and was wondering if we somehow need to catch and propagate the same error from there. Like you mentioned the way it is currently structured doesn't make that possible.

#10 lore raw

Hi Karthik!

Quoted 3 lines · Karthik Nayak

Hello Ivan,

[snip]

Quoted 6 lines

That's correct, my question was merely that we also have a refs_verify_refname_available function call there and was wondering if we somehow need to catch and propagate the same error from there. Like you mentioned the way it is currently structured doesn't make that possible.

Ah I see, that makes sense. Thanks for the clarification and for the initial question!