refs/files: use correct error type when lock exists

When fetching references into a repository, if a lock for a particular reference exists, then `lock_raw_ref()` throws: - REF_TRANSACTION_ERROR_CASE_CONFLICT: when there is a conflict because the transaction contains conflicting references while being on a case-insensitive filesystem. - REF_TRANSACTION_ERROR_GENERIC: for all other errors. The latter causes the entire set of batched updates to fail, even in case sensitive filessystems. Instead, return a 'REF_TRANSACTION_ERROR_CREATE_EXISTS' error. This allows batched updates to reject the individual update which conflicts with the existing file, while updating the rest of the references. Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Sep 17, 2025 at 17:25 UTC 9b62a67bdbeb0eb9e6bb15ef40e75bccd9b0169f
2 files changed +44 -3
refs/files-backend.c
+18 -3
@@ -797,9 +797,24 @@ retry:
797 goto retry;
798 } else {
799 unable_to_lock_message(ref_file.buf, myerr, err);
800 - if (myerr == EEXIST && ignore_case &&
801 - transaction_has_case_conflicting_update(transaction, update))
802 - ret = REF_TRANSACTION_ERROR_CASE_CONFLICT;
800 + if (myerr == EEXIST) {
801 + if (ignore_case &&
802 + transaction_has_case_conflicting_update(transaction, update)) {
803 + /*
804 + * In case-insensitive filesystems, ensure that conflicts within a
805 + * given transaction are handled. Pre-existing refs on a
806 + * case-insensitive system will be overridden without any issue.
807 + */
808 + ret = REF_TRANSACTION_ERROR_CASE_CONFLICT;
809 + } else {
810 + /*
811 + * Pre-existing case-conflicting reference locks should also be
812 + * specially categorized to avoid failing all batched updates.
813 + */
814 + ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
815 + }
816 + }
817 +
818 goto error_return;
819 }
820 }
t/t5510-fetch.sh
+26
@@ -1546,6 +1546,32 @@ test_expect_success CASE_INSENSITIVE_FS,REFFILES 'existing references in a case
1546 )
1547 '
1548
1549 +test_expect_success REFFILES 'existing reference lock in repo' '
1550 + test_when_finished rm -rf base repo &&
1551 + (
1552 + git init --ref-format=reftable base &&
1553 + cd base &&
1554 + echo >file update &&
1555 + git add . &&
1556 + git commit -m "updated" &&
1557 + git branch -M main &&
1558 +
1559 + git update-ref refs/heads/foo @ &&
1560 + git update-ref refs/heads/branch @ &&
1561 + cd .. &&
1562 +
1563 + git init --ref-format=files --bare repo &&
1564 + cd repo &&
1565 + git remote add origin ../base &&
1566 + touch refs/heads/foo.lock &&
1567 + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
1568 + test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err &&
1569 + git rev-parse refs/heads/main >expect &&
1570 + git rev-parse refs/heads/branch >actual &&
1571 + test_cmp expect actual
1572 + )
1573 +'
1574 +
1575 . "$TEST_DIRECTORY"/lib-httpd.sh
1576 start_httpd
1577