clone: handle unborn branch in bare repos

When cloning a repository with an unborn HEAD, we'll set the local HEAD to match it only if the local repository is non-bare. This is inconsistent with all other combinations: remote HEAD | local repo | local HEAD ----------------------------------------------- points to commit | non-bare | same as remote points to commit | bare | same as remote unborn | non-bare | same as remote unborn | bare | local default So I don't think this is some clever or subtle behavior, but just a bug in 4f37d45706 (clone: respect remote unborn HEAD, 2021-02-05). And it's easy to see how we ended up there. Before that commit, the code to set up the HEAD for an empty repo was guarded by "if (!option_bare)". That's because the only thing it did was call install_branch_config(), and we don't want to do so for a bare repository (unborn HEAD or not). That commit put the handling of unborn HEADs into the same block, since those also need to call install_branch_config(). But the unborn case has an additional side effect of calling create_symref(), and we want that to happen whether we are bare or not. This patch just pulls all of the "figure out the default branch" code out of the "!option_bare" block. Only the actual config installation is kept there. Note that this does mean we might allocate "ref" and not use it (if the remote is empty but did not advertise an unborn HEAD). But that's not really a big deal since this isn't a hot code path, and it keeps the code simple. The alternative would be handling unborn_head_target separately, but that gets confusing since its memory ownership is tangled up with the "ref" variable. There's just one new test, for the case we're fixing. The other ones in the table are handled elsewhere (the unborn non-bare case just above, and the actually-born cases in t5601, t5606, and t5609, as they do not require v2's "unborn" protocol extension). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Sep 20, 2021 at 15:04 UTC 6b58df54cf0f461abb35bb2fac407d2547dbc2f0
2 files changed +30 -16
builtin/clone.c
+17 -16
@@ -1340,6 +1340,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1340 our_head_points_at = remote_head_points_at;
1341 }
1342 else {
1343 + const char *branch;
1344 + char *ref;
1345 +
1346 if (option_branch)
1347 die(_("Remote branch %s not found in upstream %s"),
1348 option_branch, remote_name);
@@ -1350,24 +1353,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1353 remote_head_points_at = NULL;
1354 remote_head = NULL;
1355 option_no_checkout = 1;
1353 - if (!option_bare) {
1354 - const char *branch;
1355 - char *ref;
1356 -
1357 - if (transport_ls_refs_options.unborn_head_target &&
1358 - skip_prefix(transport_ls_refs_options.unborn_head_target,
1359 - "refs/heads/", &branch)) {
1360 - ref = transport_ls_refs_options.unborn_head_target;
1361 - transport_ls_refs_options.unborn_head_target = NULL;
1362 - create_symref("HEAD", ref, reflog_msg.buf);
1363 - } else {
1364 - branch = git_default_branch_name(0);
1365 - ref = xstrfmt("refs/heads/%s", branch);
1366 - }
1356
1368 - install_branch_config(0, branch, remote_name, ref);
1369 - free(ref);
1357 + if (transport_ls_refs_options.unborn_head_target &&
1358 + skip_prefix(transport_ls_refs_options.unborn_head_target,
1359 + "refs/heads/", &branch)) {
1360 + ref = transport_ls_refs_options.unborn_head_target;
1361 + transport_ls_refs_options.unborn_head_target = NULL;
1362 + create_symref("HEAD", ref, reflog_msg.buf);
1363 + } else {
1364 + branch = git_default_branch_name(0);
1365 + ref = xstrfmt("refs/heads/%s", branch);
1366 }
1367 +
1368 + if (!option_bare)
1369 + install_branch_config(0, branch, remote_name, ref);
1370 +
1371 + free(ref);
1372 }
1373
1374 write_refspec_config(src_ref_prefix, our_head_points_at,
t/t5702-protocol-v2.sh
+13
@@ -237,6 +237,19 @@ test_expect_success '...but not if explicitly forbidden by config' '
237 ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD
238 '
239
240 +test_expect_success 'bare clone propagates empty default branch' '
241 + test_when_finished "rm -rf file_empty_parent file_empty_child.git" &&
242 +
243 + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
244 + git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&
245 +
246 + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
247 + git -c init.defaultBranch=main -c protocol.version=2 \
248 + clone --bare \
249 + "file://$(pwd)/file_empty_parent" file_empty_child.git &&
250 + grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD
251 +'
252 +
253 test_expect_success 'fetch with file:// using protocol v2' '
254 test_when_finished "rm -f log" &&
255