Git fork

refs/files: handle D/F conflicts during locking

The previous commit added the necessary validation and checks for F/D
conflicts in the files backend when working on case insensitive systems.

There is still a possibility for D/F conflicts. This is a different from
the F/D since for F/D conflicts, there would not be a conflict during
the lock creation phase:

refs/heads/foo.lock
refs/heads/foo/bar.lock

However there would be a conflict when the locks are committed, since we
cannot have 'refs/heads/foo/bar' and 'refs/heads/foo'. These kinds of
conflicts are checked and resolved in
`refs_verify_refnames_available()`, so the previous commit ensured that
for case-insensitive filesystems, we would lowercase the inputs to that
function.

For D/F conflicts, there is a conflict during the lock creation phase
itself:

refs/heads/foo/bar.lock
refs/heads/foo.lock

As in `lock_raw_ref()` after creating the lock, we also check for D/F
conflicts. This can occur in case-insensitive filesystems when trying to
fetch case-conflicted references like:

refs/heads/Foo/new
refs/heads/foo

D/F conflicts can also occur in case-sensitive filesystems, when the
repository already contains a directory with a lock file
'refs/heads/foo/bar.lock' and trying to fetch 'refs/heads/foo'. This
doesn't concern directories containing garbage files as those are
handled on a higher level.

To fix this, simply categorize the error as a name conflict. Also remove
this reference from the list of valid refnames for availability checks.
By categorizing the error and removing it from the list of valid
references, batched updates now knows to reject such reference updates
and apply the other reference updates.

Fix a small typo in `ref_transaction_maybe_set_rejected()` while here.

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Karthik Nayak and committed by
Junio C Hamano
948b2ab0 770f389b

+60 -6
+8 -1
refs.c
··· 1223 1223 return 0; 1224 1224 1225 1225 if (!transaction->rejections) 1226 - BUG("transaction not inititalized with failure support"); 1226 + BUG("transaction not initialized with failure support"); 1227 1227 1228 1228 /* 1229 1229 * Don't accept generic errors, since these errors are not user ··· 1231 1231 */ 1232 1232 if (err == REF_TRANSACTION_ERROR_GENERIC) 1233 1233 return 0; 1234 + 1235 + /* 1236 + * Rejected refnames shouldn't be considered in the availability 1237 + * checks, so remove them from the list. 1238 + */ 1239 + string_list_remove(&transaction->refnames, 1240 + transaction->updates[update_idx]->refname, 0); 1234 1241 1235 1242 transaction->updates[update_idx]->rejection_err = err; 1236 1243 ALLOC_GROW(transaction->rejections->update_indices,
+6 -5
refs/files-backend.c
··· 870 870 goto error_return; 871 871 } else if (remove_dir_recursively(&ref_file, 872 872 REMOVE_DIR_EMPTY_ONLY)) { 873 + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; 873 874 if (refs_verify_refname_available( 874 875 &refs->base, refname, 875 876 extras, NULL, 0, err)) { ··· 877 878 * The error message set by 878 879 * verify_refname_available() is OK. 879 880 */ 880 - ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; 881 881 goto error_return; 882 882 } else { 883 883 /* 884 - * We can't delete the directory, 885 - * but we also don't know of any 886 - * references that it should 887 - * contain. 884 + * Directory conflicts can occur if there 885 + * is an existing lock file in the directory 886 + * or if the filesystem is case-insensitive 887 + * and the directory contains a valid reference 888 + * but conflicts with the update. 888 889 */ 889 890 strbuf_addf(err, "there is a non-empty directory '%s' " 890 891 "blocking reference '%s'",
+46
t/t5510-fetch.sh
··· 59 59 cd case_sensitive_fd && 60 60 git branch foo/bar && 61 61 git branch Foo 62 + ) && 63 + git clone --ref-format=reftable . case_sensitive_df && 64 + ( 65 + cd case_sensitive_df && 66 + git branch Foo/bar && 67 + git branch foo 62 68 ) 63 69 ' 64 70 ··· 1588 1594 test_grep "failed: refname conflict" err && 1589 1595 git rev-parse refs/heads/main >expect && 1590 1596 git rev-parse refs/heads/foo/bar >actual && 1597 + test_cmp expect actual 1598 + ) 1599 + ' 1600 + 1601 + test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensitive filesystem' ' 1602 + test_when_finished rm -rf case_insensitive && 1603 + ( 1604 + git init --bare case_insensitive && 1605 + cd case_insensitive && 1606 + git remote add origin -- ../case_sensitive_df && 1607 + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && 1608 + test_grep "failed: refname conflict" err && 1609 + git rev-parse refs/heads/main >expect && 1610 + git rev-parse refs/heads/Foo/bar >actual && 1611 + test_cmp expect actual 1612 + ) 1613 + ' 1614 + 1615 + test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' ' 1616 + ( 1617 + git init --ref-format=reftable base && 1618 + cd base && 1619 + echo >file update && 1620 + git add . && 1621 + git commit -m "updated" && 1622 + git branch -M main && 1623 + 1624 + git update-ref refs/heads/foo @ && 1625 + git update-ref refs/heads/branch @ && 1626 + cd .. && 1627 + 1628 + git init --ref-format=files --bare repo && 1629 + cd repo && 1630 + git remote add origin ../base && 1631 + mkdir refs/heads/foo && 1632 + touch refs/heads/foo/random.lock && 1633 + test_must_fail git fetch origin "refs/heads/*:refs/heads/*" 2>err && 1634 + test_grep "some local refs could not be updated; try running" err && 1635 + git rev-parse refs/heads/main >expect && 1636 + git rev-parse refs/heads/branch >actual && 1591 1637 test_cmp expect actual 1592 1638 ) 1593 1639 '