Skip to content

Commit f8fb971

Browse files
committed
fetch: ignore wildcarded refspecs that update local symbolic refs
In a repository cloned from somewhere else, you typically have a symbolic ref refs/remotes/origin/HEAD pointing at the 'master' remote-tracking ref that is next to it. When fetching into such a repository with "git fetch --mirror" from another repository that was similarly cloned, the implied wildcard refspec refs/*:refs/* will end up asking to update refs/remotes/origin/HEAD with the object at refs/remotes/origin/HEAD at the remote side, while asking to update refs/remotes/origin/master the same way. Depending on the order the two updates happen, the latter one would find that the value of the ref before it is updated has changed from what the code expects. When the user asks to update the underlying ref via the symbolic ref explicitly without using a wildcard refspec, e.g. "git fetch $there refs/heads/master:refs/remotes/origin/HEAD", we should still let him do so, but when expanding wildcard refs, it will result in a more intuitive outcome if we simply ignore local symbolic refs. As the purpose of the symbolic ref refs/remotes/origin/HEAD is to follow the ref it points at (e.g. refs/remotes/origin/master), its value would change when the underlying ref is updated. Earlier commit da3efdb (receive-pack: detect aliased updates which can occur with symrefs, 2010-04-19) fixed a similar issue for "git push". Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3e53891 commit f8fb971

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

remote.c

+12-1
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,16 @@ int branch_merge_matches(struct branch *branch,
13701370
return refname_match(branch->merge[i]->src, refname, ref_fetch_rules);
13711371
}
13721372

1373+
static int ignore_symref_update(const char *refname)
1374+
{
1375+
unsigned char sha1[20];
1376+
int flag;
1377+
1378+
if (!resolve_ref_unsafe(refname, sha1, 0, &flag))
1379+
return 0; /* non-existing refs are OK */
1380+
return (flag & REF_ISSYMREF);
1381+
}
1382+
13731383
static struct ref *get_expanded_map(const struct ref *remote_refs,
13741384
const struct refspec *refspec)
13751385
{
@@ -1383,7 +1393,8 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
13831393
if (strchr(ref->name, '^'))
13841394
continue; /* a dereference item */
13851395
if (match_name_with_pattern(refspec->src, ref->name,
1386-
refspec->dst, &expn_name)) {
1396+
refspec->dst, &expn_name) &&
1397+
!ignore_symref_update(expn_name)) {
13871398
struct ref *cpy = copy_ref(ref);
13881399

13891400
cpy->peer_ref = alloc_ref(expn_name);

t/t5535-fetch-push-symref.sh

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/sh
2+
3+
test_description='avoiding conflicting update thru symref aliasing'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup' '
8+
test_commit one &&
9+
git clone . src &&
10+
git clone src dst1 &&
11+
git clone src dst2 &&
12+
test_commit two &&
13+
( cd src && git pull )
14+
'
15+
16+
test_expect_success 'push' '
17+
(
18+
cd src &&
19+
git push ../dst1 "refs/remotes/*:refs/remotes/*"
20+
) &&
21+
git ls-remote src "refs/remotes/*" >expect &&
22+
git ls-remote dst1 "refs/remotes/*" >actual &&
23+
test_cmp expect actual &&
24+
( cd src && git symbolic-ref refs/remotes/origin/HEAD ) >expect &&
25+
( cd dst1 && git symbolic-ref refs/remotes/origin/HEAD ) >actual &&
26+
test_cmp expect actual
27+
'
28+
29+
test_expect_success 'fetch' '
30+
(
31+
cd dst2 &&
32+
git fetch ../src "refs/remotes/*:refs/remotes/*"
33+
) &&
34+
git ls-remote src "refs/remotes/*" >expect &&
35+
git ls-remote dst2 "refs/remotes/*" >actual &&
36+
test_cmp expect actual &&
37+
( cd src && git symbolic-ref refs/remotes/origin/HEAD ) >expect &&
38+
( cd dst2 && git symbolic-ref refs/remotes/origin/HEAD ) >actual &&
39+
test_cmp expect actual
40+
'
41+
42+
test_done

0 commit comments

Comments
 (0)