Skip to content

Commit 9196a2f

Browse files
committed
Merge branch 'jc/upload-pack-send-symref' into maint
One long-standing flaw in the pack transfer protocol used by "git clone" was that there was no way to tell the other end which branch "HEAD" points at, and the receiving end needed to guess. A new capability has been defined in the pack protocol to convey this information so that cloning from a repository with more than one branches pointing at the same commit where the HEAD is at now reliably sets the initial branch in the resulting repository. * jc/upload-pack-send-symref: t5570: Update for clone-progress-to-stderr branch t5570: Update for symref capability clone: test the new HEAD detection logic connect: annotate refs with their symref information in get_remote_head() connect.c: make parse_feature_value() static upload-pack: send non-HEAD symbolic refs upload-pack: send symbolic ref information as capability upload-pack.c: do not pass confusing cb_data to mark_our_ref() t5505: fix "set-head --auto with ambiguous HEAD" test
2 parents e5becd0 + 360a326 commit 9196a2f

File tree

6 files changed

+124
-20
lines changed

6 files changed

+124
-20
lines changed

cache.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,6 @@ extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
11071107
extern int server_supports(const char *feature);
11081108
extern int parse_feature_request(const char *features, const char *feature);
11091109
extern const char *server_feature_value(const char *feature, int *len_ret);
1110-
extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
11111110

11121111
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
11131112

connect.c

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
#include "run-command.h"
77
#include "remote.h"
88
#include "url.h"
9+
#include "string-list.h"
910

1011
static char *server_capabilities;
12+
static const char *parse_feature_value(const char *, const char *, int *);
1113

1214
static int check_ref(const char *name, int len, unsigned int flags)
1315
{
@@ -59,13 +61,69 @@ static void die_initial_contact(int got_at_least_one_head)
5961
"and the repository exists.");
6062
}
6163

64+
static void parse_one_symref_info(struct string_list *symref, const char *val, int len)
65+
{
66+
char *sym, *target;
67+
struct string_list_item *item;
68+
69+
if (!len)
70+
return; /* just "symref" */
71+
/* e.g. "symref=HEAD:refs/heads/master" */
72+
sym = xmalloc(len + 1);
73+
memcpy(sym, val, len);
74+
sym[len] = '\0';
75+
target = strchr(sym, ':');
76+
if (!target)
77+
/* just "symref=something" */
78+
goto reject;
79+
*(target++) = '\0';
80+
if (check_refname_format(sym, REFNAME_ALLOW_ONELEVEL) ||
81+
check_refname_format(target, REFNAME_ALLOW_ONELEVEL))
82+
/* "symref=bogus:pair */
83+
goto reject;
84+
item = string_list_append(symref, sym);
85+
item->util = target;
86+
return;
87+
reject:
88+
free(sym);
89+
return;
90+
}
91+
92+
static void annotate_refs_with_symref_info(struct ref *ref)
93+
{
94+
struct string_list symref = STRING_LIST_INIT_DUP;
95+
const char *feature_list = server_capabilities;
96+
97+
while (feature_list) {
98+
int len;
99+
const char *val;
100+
101+
val = parse_feature_value(feature_list, "symref", &len);
102+
if (!val)
103+
break;
104+
parse_one_symref_info(&symref, val, len);
105+
feature_list = val + 1;
106+
}
107+
sort_string_list(&symref);
108+
109+
for (; ref; ref = ref->next) {
110+
struct string_list_item *item;
111+
item = string_list_lookup(&symref, ref->name);
112+
if (!item)
113+
continue;
114+
ref->symref = xstrdup((char *)item->util);
115+
}
116+
string_list_clear(&symref, 0);
117+
}
118+
62119
/*
63120
* Read all the refs from the other end
64121
*/
65122
struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
66123
struct ref **list, unsigned int flags,
67124
struct extra_have_objects *extra_have)
68125
{
126+
struct ref **orig_list = list;
69127
int got_at_least_one_head = 0;
70128

71129
*list = NULL;
@@ -113,10 +171,13 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
113171
list = &ref->next;
114172
got_at_least_one_head = 1;
115173
}
174+
175+
annotate_refs_with_symref_info(*orig_list);
176+
116177
return list;
117178
}
118179

119-
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
180+
static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
120181
{
121182
int len;
122183

t/t5505-remote.sh

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,7 @@ cat >test/expect <<EOF
160160
* remote two
161161
Fetch URL: ../two
162162
Push URL: ../three
163-
HEAD branch (remote HEAD is ambiguous, may be one of the following):
164-
another
165-
master
163+
HEAD branch: master
166164
Local refs configured for 'git push':
167165
ahead forces to master (fast-forwardable)
168166
master pushes to another (up to date)
@@ -262,16 +260,12 @@ test_expect_success 'set-head --auto' '
262260
)
263261
'
264262

265-
cat >test/expect <<\EOF
266-
error: Multiple remote HEAD branches. Please choose one explicitly with:
267-
git remote set-head two another
268-
git remote set-head two master
269-
EOF
270-
271-
test_expect_success 'set-head --auto fails w/multiple HEADs' '
263+
test_expect_success 'set-head --auto has no problem w/multiple HEADs' '
272264
(
273265
cd test &&
274-
test_must_fail git remote set-head --auto two >output 2>&1 &&
266+
git fetch two "refs/heads/*:refs/remotes/two/*" &&
267+
git remote set-head --auto two >output 2>&1 &&
268+
echo "two/HEAD set to master" >expect &&
275269
test_i18ncmp expect output
276270
)
277271
'

t/t5570-git-daemon.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test_expect_success 'fetch changes via git protocol' '
3737
test_cmp file clone/file
3838
'
3939

40-
test_expect_failure 'remote detects correct HEAD' '
40+
test_expect_success 'remote detects correct HEAD' '
4141
git push public master:other &&
4242
(cd clone &&
4343
git remote set-head -d origin &&

t/t5601-clone.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,15 @@ test_expect_success NOT_MINGW,NOT_CYGWIN 'clone local path foo:bar' '
285285
git clone "./foo:bar" foobar
286286
'
287287

288+
test_expect_success 'clone from a repository with two identical branches' '
289+
290+
(
291+
cd src &&
292+
git checkout -b another master
293+
) &&
294+
git clone src target-11 &&
295+
test "z$( cd target-11 && git symbolic-ref HEAD )" = zrefs/heads/another
296+
297+
'
298+
288299
test_done

upload-pack.c

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,16 @@ static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag
688688
return 0;
689689
}
690690

691+
static void format_symref_info(struct strbuf *buf, struct string_list *symref)
692+
{
693+
struct string_list_item *item;
694+
695+
if (!symref->nr)
696+
return;
697+
for_each_string_list_item(item, symref)
698+
strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
699+
}
700+
691701
static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
692702
{
693703
static const char *capabilities = "multi_ack thin-pack side-band"
@@ -696,35 +706,64 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
696706
const char *refname_nons = strip_namespace(refname);
697707
unsigned char peeled[20];
698708

699-
if (mark_our_ref(refname, sha1, flag, cb_data))
709+
if (mark_our_ref(refname, sha1, flag, NULL))
700710
return 0;
701711

702-
if (capabilities)
703-
packet_write(1, "%s %s%c%s%s%s agent=%s\n",
712+
if (capabilities) {
713+
struct strbuf symref_info = STRBUF_INIT;
714+
715+
format_symref_info(&symref_info, cb_data);
716+
packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
704717
sha1_to_hex(sha1), refname_nons,
705718
0, capabilities,
706719
allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
707720
stateless_rpc ? " no-done" : "",
721+
symref_info.buf,
708722
git_user_agent_sanitized());
709-
else
723+
strbuf_release(&symref_info);
724+
} else {
710725
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
726+
}
711727
capabilities = NULL;
712728
if (!peel_ref(refname, peeled))
713729
packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
714730
return 0;
715731
}
716732

733+
static int find_symref(const char *refname, const unsigned char *sha1, int flag,
734+
void *cb_data)
735+
{
736+
const char *symref_target;
737+
struct string_list_item *item;
738+
unsigned char unused[20];
739+
740+
if ((flag & REF_ISSYMREF) == 0)
741+
return 0;
742+
symref_target = resolve_ref_unsafe(refname, unused, 0, &flag);
743+
if (!symref_target || (flag & REF_ISSYMREF) == 0)
744+
die("'%s' is a symref but it is not?", refname);
745+
item = string_list_append(cb_data, refname);
746+
item->util = xstrdup(symref_target);
747+
return 0;
748+
}
749+
717750
static void upload_pack(void)
718751
{
752+
struct string_list symref = STRING_LIST_INIT_DUP;
753+
754+
head_ref_namespaced(find_symref, &symref);
755+
for_each_namespaced_ref(find_symref, &symref);
756+
719757
if (advertise_refs || !stateless_rpc) {
720758
reset_timeout();
721-
head_ref_namespaced(send_ref, NULL);
722-
for_each_namespaced_ref(send_ref, NULL);
759+
head_ref_namespaced(send_ref, &symref);
760+
for_each_namespaced_ref(send_ref, &symref);
723761
packet_flush(1);
724762
} else {
725763
head_ref_namespaced(mark_our_ref, NULL);
726764
for_each_namespaced_ref(mark_our_ref, NULL);
727765
}
766+
string_list_clear(&symref, 1);
728767
if (advertise_refs)
729768
return;
730769

0 commit comments

Comments
 (0)