Skip to content

Commit 9907d13

Browse files
committed
Merge branch 'jc/upload-pack-send-symref'
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 177f0a4 + 360a326 commit 9907d13

File tree

6 files changed

+125
-22
lines changed

6 files changed

+125
-22
lines changed

connect.c

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
#include "remote.h"
88
#include "connect.h"
99
#include "url.h"
10+
#include "string-list.h"
1011

1112
static char *server_capabilities;
13+
static const char *parse_feature_value(const char *, const char *, int *);
1214

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

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

72130
*list = NULL;
@@ -114,10 +172,13 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
114172
list = &ref->next;
115173
got_at_least_one_head = 1;
116174
}
175+
176+
annotate_refs_with_symref_info(*orig_list);
177+
117178
return list;
118179
}
119180

120-
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
181+
static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
121182
{
122183
int len;
123184

connect.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ extern int git_connection_is_socket(struct child_process *conn);
88
extern int server_supports(const char *feature);
99
extern int parse_feature_request(const char *features, const char *feature);
1010
extern const char *server_feature_value(const char *feature, int *len_ret);
11-
extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
1211

1312
#endif

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: 2 additions & 3 deletions
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 &&
@@ -122,8 +122,7 @@ test_remote_error()
122122
fi
123123

124124
test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" "$@" 2>output &&
125-
echo "fatal: remote error: $msg: /$repo" >expect &&
126-
test_cmp expect output
125+
test_i18ngrep "fatal: remote error: $msg: /$repo" output &&
127126
ret=$?
128127
chmod +x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git"
129128
(exit $ret)

t/t5601-clone.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,15 @@ test_expect_success 'bracketed hostnames are still ssh' '
329329
expect_ssh myhost:123 src
330330
'
331331

332+
test_expect_success 'clone from a repository with two identical branches' '
333+
334+
(
335+
cd src &&
336+
git checkout -b another master
337+
) &&
338+
git clone src target-11 &&
339+
test "z$( cd target-11 && git symbolic-ref HEAD )" = zrefs/heads/another
340+
341+
'
342+
332343
test_done

upload-pack.c

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

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

700-
if (mark_our_ref(refname, sha1, flag, cb_data))
710+
if (mark_our_ref(refname, sha1, flag, NULL))
701711
return 0;
702712

703-
if (capabilities)
704-
packet_write(1, "%s %s%c%s%s%s agent=%s\n",
713+
if (capabilities) {
714+
struct strbuf symref_info = STRBUF_INIT;
715+
716+
format_symref_info(&symref_info, cb_data);
717+
packet_write(1, "%s %s%c%s%s%s%s agent=%s\n",
705718
sha1_to_hex(sha1), refname_nons,
706719
0, capabilities,
707720
allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "",
708721
stateless_rpc ? " no-done" : "",
722+
symref_info.buf,
709723
git_user_agent_sanitized());
710-
else
724+
strbuf_release(&symref_info);
725+
} else {
711726
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
727+
}
712728
capabilities = NULL;
713729
if (!peel_ref(refname, peeled))
714730
packet_write(1, "%s %s^{}\n", sha1_to_hex(peeled), refname_nons);
715731
return 0;
716732
}
717733

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

0 commit comments

Comments
 (0)