Skip to content

Commit 4f37d45

Browse files
jonathantanmygitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3983540 commit 4f37d45

File tree

6 files changed

+79
-8
lines changed

6 files changed

+79
-8
lines changed

Documentation/config/init.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ init.templateDir::
44

55
init.defaultBranch::
66
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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
13301330
remote_head = NULL;
13311331
option_no_checkout = 1;
13321332
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+
}
13351346

13361347
install_branch_config(0, branch, remote_name, ref);
13371348
free(ref);
@@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
13851396
junk_mode = JUNK_LEAVE_ALL;
13861397

13871398
strvec_clear(&transport_ls_refs_options.ref_prefixes);
1399+
free(transport_ls_refs_options.unborn_head_target);
13881400
return err;
13891401
}

connect.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,
376376
}
377377

378378
/* 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)
380381
{
381382
int ret = 1;
382383
int i = 0;
@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)
397398
goto out;
398399
}
399400

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+
}
400420
if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||
401421
*end) {
402422
ret = 0;
@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
461481
const char *hash_name;
462482
struct strvec *ref_prefixes = transport_options ?
463483
&transport_options->ref_prefixes : NULL;
484+
char **unborn_head_target = transport_options ?
485+
&transport_options->unborn_head_target : NULL;
464486
*list = NULL;
465487

466488
if (server_supports_v2("ls-refs", 1))
@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
490512
if (!for_push)
491513
packet_write_fmt(fd_out, "peel\n");
492514
packet_write_fmt(fd_out, "symrefs\n");
515+
if (server_supports_feature("ls-refs", "unborn", 0))
516+
packet_write_fmt(fd_out, "unborn\n");
493517
for (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {
494518
packet_write_fmt(fd_out, "ref-prefix %s\n",
495519
ref_prefixes->v[i]);
@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
498522

499523
/* Process response from server */
500524
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))
502526
die(_("invalid ls-refs response: %s"), reader->line);
503527
}
504528

t/t5606-clone-options.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '
102102
'
103103

104104
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 &&
106108
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
107109
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)
110112
'
111113

112114
test_expect_success 'guesses initial branch name correctly' '

t/t5702-protocol-v2.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '
209209
grep "ref-prefix refs/tags/" log
210210
'
211211

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+
212237
test_expect_success 'fetch with file:// using protocol v2' '
213238
test_when_finished "rm -f log" &&
214239

transport.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,14 @@ struct transport_ls_refs_options {
243243
* provided ref_prefixes.
244244
*/
245245
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;
246254
};
247255
#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }
248256

0 commit comments

Comments
 (0)