clone: respect remote unborn HEAD
Teach Git to use the "unborn" feature introduced in a previous patch as follows: Git will always send the "unborn" argument if it is supported by the server. During "git clone", if cloning an empty repository, Git will use the new information to determine the local branch to create. In all other cases, Git will ignore it. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jonathan Tan committed
Feb 5, 2021 at 12:48 UTC
4f37d45706514a4b3d0259d26f719678a0cf3521
6 files changed
+79
-8
Documentation/config/init.txt
+1
-1
@@ -4,4 +4,4 @@ init.templateDir::
4
5
init.defaultBranch::
6
Allows overriding the default branch name e.g. when initializing
7
- a new repository or when cloning an empty repository.
7
+ a new repository.
builtin/clone.c
+14
-2
@@ -1330,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1330
remote_head = NULL;
1331
option_no_checkout = 1;
1332
if (!option_bare) {
1333
- const char *branch = git_default_branch_name();
1334
- char *ref = xstrfmt("refs/heads/%s", branch);
1333
+ const char *branch;
1334
+ char *ref;
1335
+
1336
+ if (transport_ls_refs_options.unborn_head_target &&
1337
+ skip_prefix(transport_ls_refs_options.unborn_head_target,
1338
+ "refs/heads/", &branch)) {
1339
+ ref = transport_ls_refs_options.unborn_head_target;
1340
+ transport_ls_refs_options.unborn_head_target = NULL;
1341
+ create_symref("HEAD", ref, reflog_msg.buf);
1342
+ } else {
1343
+ branch = git_default_branch_name();
1344
+ ref = xstrfmt("refs/heads/%s", branch);
1345
+ }
1346
1347
install_branch_config(0, branch, remote_name, ref);
1348
free(ref);
@@ -1385,5 +1396,6 @@ cleanup:
1396
junk_mode = JUNK_LEAVE_ALL;
1397
1398
strvec_clear(&transport_ls_refs_options.ref_prefixes);
1399
+ free(transport_ls_refs_options.unborn_head_target);
1400
return err;
1401
}
connect.c
+26
-2
@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,
376
}
377
378
/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
379
-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)
379
+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,
380
+ char **unborn_head_target)
381
{
382
int ret = 1;
383
int i = 0;
@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)
398
goto out;
399
}
400
401
+ if (!strcmp("unborn", line_sections.items[i].string)) {
402
+ i++;
403
+ if (unborn_head_target &&
404
+ !strcmp("HEAD", line_sections.items[i++].string)) {
405
+ /*
406
+ * Look for the symref target (if any). If found,
407
+ * return it to the caller.
408
+ */
409
+ for (; i < line_sections.nr; i++) {
410
+ const char *arg = line_sections.items[i].string;
411
+
412
+ if (skip_prefix(arg, "symref-target:", &arg)) {
413
+ *unborn_head_target = xstrdup(arg);
414
+ break;
415
+ }
416
+ }
417
+ }
418
+ goto out;
419
+ }
420
if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||
421
*end) {
422
ret = 0;
@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
481
const char *hash_name;
482
struct strvec *ref_prefixes = transport_options ?
483
&transport_options->ref_prefixes : NULL;
484
+ char **unborn_head_target = transport_options ?
485
+ &transport_options->unborn_head_target : NULL;
486
*list = NULL;
487
488
if (server_supports_v2("ls-refs", 1))
@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
512
if (!for_push)
513
packet_write_fmt(fd_out, "peel\n");
514
packet_write_fmt(fd_out, "symrefs\n");
515
+ if (server_supports_feature("ls-refs", "unborn", 0))
516
+ packet_write_fmt(fd_out, "unborn\n");
517
for (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {
518
packet_write_fmt(fd_out, "ref-prefix %s\n",
519
ref_prefixes->v[i]);
@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
522
523
/* Process response from server */
524
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
501
- if (!process_ref_v2(reader, &list))
525
+ if (!process_ref_v2(reader, &list, unborn_head_target))
526
die(_("invalid ls-refs response: %s"), reader->line);
527
}
528
t/t5606-clone-options.sh
+5
-3
@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '
102
'
103
104
test_expect_success 'chooses correct default initial branch name' '
105
- git init --bare empty &&
105
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
106
+ git -c init.defaultBranch=foo init --bare empty &&
107
+ test_config -C empty lsrefs.unborn advertise &&
108
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
109
git -c init.defaultBranch=up clone empty whats-up &&
108
- test refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&
109
- test refs/heads/up = $(git -C whats-up config branch.up.merge)
110
+ test refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&
111
+ test refs/heads/foo = $(git -C whats-up config branch.foo.merge)
112
'
113
114
test_expect_success 'guesses initial branch name correctly' '
t/t5702-protocol-v2.sh
+25
@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '
209
grep "ref-prefix refs/tags/" log
210
'
211
212
+test_expect_success 'clone of empty repo propagates name of default branch' '
213
+ test_when_finished "rm -rf file_empty_parent file_empty_child" &&
214
+
215
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
216
+ git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&
217
+
218
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
219
+ git -c init.defaultBranch=main -c protocol.version=2 \
220
+ clone "file://$(pwd)/file_empty_parent" file_empty_child &&
221
+ grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD
222
+'
223
+
224
+test_expect_success '...but not if explicitly forbidden by config' '
225
+ test_when_finished "rm -rf file_empty_parent file_empty_child" &&
226
+
227
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
228
+ git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&
229
+ test_config -C file_empty_parent lsrefs.unborn ignore &&
230
+
231
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
232
+ git -c init.defaultBranch=main -c protocol.version=2 \
233
+ clone "file://$(pwd)/file_empty_parent" file_empty_child &&
234
+ ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD
235
+'
236
+
237
test_expect_success 'fetch with file:// using protocol v2' '
238
test_when_finished "rm -f log" &&
239
transport.h
+8
@@ -243,6 +243,14 @@ struct transport_ls_refs_options {
243
* provided ref_prefixes.
244
*/
245
struct strvec ref_prefixes;
246
+
247
+ /*
248
+ * If unborn_head_target is not NULL, and the remote reports HEAD as
249
+ * pointing to an unborn branch, transport_get_remote_refs() stores the
250
+ * unborn branch in unborn_head_target. It should be freed by the
251
+ * caller.
252
+ */
253
+ char *unborn_head_target;
254
};
255
#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }
256