Skip to content

Commit ad95085

Browse files
committed
Merge branch 'kn/refs-files-case-insensitive' into seen
* kn/refs-files-case-insensitive: refs/files: handle F/D conflicts in case-insensitive FS refs/files: use correct error type when locking fails
2 parents c882f27 + c141058 commit ad95085

4 files changed

Lines changed: 102 additions & 6 deletions

File tree

builtin/fetch.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,8 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
16431643

16441644
struct ref_rejection_data {
16451645
int *retcode;
1646-
int conflict_msg_shown;
1646+
bool conflict_msg_shown;
1647+
bool case_sensitive_msg_shown;
16471648
const char *remote_name;
16481649
};
16491650

@@ -1657,11 +1658,25 @@ static void ref_transaction_rejection_handler(const char *refname,
16571658
{
16581659
struct ref_rejection_data *data = cb_data;
16591660

1660-
if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT && !data->conflict_msg_shown) {
1661+
if (err == REF_TRANSACTION_ERROR_CREATE_EXISTS && ignore_case &&
1662+
!data->case_sensitive_msg_shown) {
1663+
error(_("You're on a case-insensitive filesystem, and the remote you are\n"
1664+
"trying to fetch from has references that only differ in casing. It\n"
1665+
"is impossible to store such references with the 'files' backend. You\n"
1666+
"can either accept this as-is, in which case you won't be able to\n"
1667+
"store all remote references on disk. Or you can alternatively\n"
1668+
"migrate your repository to use the 'reftable' backend with the\n"
1669+
"following command:\n\n git refs migrate --ref-format=reftable\n\n"
1670+
"Please keep in mind that not all implementations of Git support this\n"
1671+
"new format yet. So if you use tools other than Git to access this\n"
1672+
"repository it may not be an option to migrate to reftables.\n"));
1673+
data->case_sensitive_msg_shown = true;
1674+
} else if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT &&
1675+
!data->conflict_msg_shown) {
16611676
error(_("some local refs could not be updated; try running\n"
16621677
" 'git remote prune %s' to remove any old, conflicting "
16631678
"branches"), data->remote_name);
1664-
data->conflict_msg_shown = 1;
1679+
data->conflict_msg_shown = true;
16651680
} else {
16661681
const char *reason = ref_transaction_error_msg(err);
16671682

refs/files-backend.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,8 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
782782
goto retry;
783783
} else {
784784
unable_to_lock_message(ref_file.buf, myerr, err);
785+
if (myerr == EEXIST)
786+
ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
785787
goto error_return;
786788
}
787789
}
@@ -873,8 +875,23 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
873875
* If the ref did not exist and we are creating it, we have to
874876
* make sure there is no existing packed ref that conflicts
875877
* with refname. This check is deferred so that we can batch it.
878+
*
879+
* For case-insensitive filesystems, we should also check for F/D
880+
* conflicts between 'foo' and 'Foo/bar'. So let's lowercase
881+
* the refname.
876882
*/
877-
item = string_list_append(refnames_to_check, refname);
883+
if (ignore_case) {
884+
struct strbuf lower = STRBUF_INIT;
885+
886+
strbuf_addstr(&lower, refname);
887+
strbuf_tolower(&lower);
888+
889+
item = string_list_append(refnames_to_check, lower.buf);
890+
strbuf_release(&lower);
891+
} else {
892+
item = string_list_append(refnames_to_check, refname);
893+
}
894+
878895
item->util = xmalloc(sizeof(update_idx));
879896
memcpy(item->util, &update_idx, sizeof(update_idx));
880897
}
@@ -2858,7 +2875,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
28582875
"ref_transaction_prepare");
28592876
size_t i;
28602877
int ret = 0;
2861-
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
2878+
struct string_list refnames_to_check = STRING_LIST_INIT_DUP;
28622879
char *head_ref = NULL;
28632880
int head_type;
28642881
struct files_transaction_backend_data *backend_data;

t/t1400-update-ref.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,30 @@ do
22942294
)
22952295
'
22962296

2297+
test_expect_success CASE_INSENSITIVE_FS,REFFILES "stdin $type batch-updates existing reference" '
2298+
git init repo &&
2299+
test_when_finished "rm -fr repo" &&
2300+
(
2301+
cd repo &&
2302+
test_commit one &&
2303+
old_head=$(git rev-parse HEAD) &&
2304+
test_commit two &&
2305+
head=$(git rev-parse HEAD) &&
2306+
2307+
format_command $type "create refs/heads/foo" "$head" >stdin &&
2308+
format_command $type "create refs/heads/ref" "$old_head" >>stdin &&
2309+
format_command $type "create refs/heads/Foo" "$old_head" >>stdin &&
2310+
git update-ref $type --stdin --batch-updates <stdin >stdout &&
2311+
2312+
echo $head >expect &&
2313+
git rev-parse refs/heads/foo >actual &&
2314+
echo $old_head >expect &&
2315+
git rev-parse refs/heads/ref >actual &&
2316+
test_cmp expect actual &&
2317+
test_grep -q "reference already exists" stdout
2318+
)
2319+
'
2320+
22972321
test_expect_success "stdin $type batch-updates delete incorrect symbolic ref" '
22982322
git init repo &&
22992323
test_when_finished "rm -fr repo" &&

t/t5510-fetch.sh

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,19 @@ test_expect_success "clone and setup child repos" '
4545
git config set branch.main.merge refs/heads/one
4646
) &&
4747
git clone . bundle &&
48-
git clone . seven
48+
git clone . seven &&
49+
git clone --ref-format=reftable . case_sensitive &&
50+
(
51+
cd case_sensitive &&
52+
git branch branch1 &&
53+
git branch bRanch1
54+
) &&
55+
git clone --ref-format=reftable . case_sensitive_fd &&
56+
(
57+
cd case_sensitive_fd &&
58+
git branch foo/bar &&
59+
git branch Foo
60+
)
4961
'
5062

5163
test_expect_success "fetch test" '
@@ -1465,6 +1477,34 @@ test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' '
14651477
test_path_is_missing whoops
14661478
'
14671479

1480+
test_expect_success CASE_INSENSITIVE_FS,REFFILES 'existing references in a case insensitive filesystem' '
1481+
test_when_finished rm -rf case_insensitive &&
1482+
(
1483+
git init --bare case_insensitive &&
1484+
cd case_insensitive &&
1485+
git remote add origin -- ../case_sensitive &&
1486+
test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
1487+
test_grep "You${SQ}re on a case-insensitive filesystem" err &&
1488+
git rev-parse refs/heads/main >expect &&
1489+
git rev-parse refs/heads/branch1 >actual &&
1490+
test_cmp expect actual
1491+
)
1492+
'
1493+
1494+
test_expect_success CASE_INSENSITIVE_FS,REFFILES 'F/D conflict on case insensitive filesystem' '
1495+
test_when_finished rm -rf case_insensitive &&
1496+
(
1497+
git init --bare case_insensitive &&
1498+
cd case_insensitive &&
1499+
git remote add origin -- ../case_sensitive_fd &&
1500+
test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err &&
1501+
test_grep "failed: refname conflict" err &&
1502+
git rev-parse refs/heads/main >expect &&
1503+
git rev-parse refs/heads/foo/bar >actual &&
1504+
test_cmp expect actual
1505+
)
1506+
'
1507+
14681508
. "$TEST_DIRECTORY"/lib-httpd.sh
14691509
start_httpd
14701510

0 commit comments

Comments
 (0)