All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jeff King <peff@peff.net>
To: git@vger.kernel.org
Cc: Jonathan Tan <jonathantanmy@google.com>
Subject: [PATCH 3/3] clone: propagate empty remote HEAD even with other branches
Date: Wed, 6 Jul 2022 04:03:37 -0400	[thread overview]
Message-ID: <YsVB2eOMp5guQfVr@coredump.intra.peff.net> (raw)
In-Reply-To: <YsVAfudoUA5YkQWH@coredump.intra.peff.net>

Unless "--branch" was given, clone generally tries to match the local
HEAD to the remote one. For most repositories, this is easy: the remote
tells us which branch HEAD was pointing to, and we call our local
checkout() function on that branch.

When cloning an empty repository, it's a little more tricky: we have
special code that checks the transport's "unborn" extension, or falls back
to our local idea of what the default branch should be. In either case,
we point the new HEAD to that, and set up the branch.* config.

But that leaves one case unhandled: when the remote repository _isn't_
empty, but its HEAD is unborn. The checkout() function is smart enough
to realize we didn't fetch the remote HEAD and it bails with a warning.
But we'll have ignored any information the remote gave us via the unborn
extension. This leads to nonsense outcomes:

  - If the remote has its HEAD pointing to an unborn "foo" and contains
    another branch "bar", cloning will get branch "bar" but leave the
    local HEAD pointing at "master" (or whatever our local default is),
    which is useless. The project does not use "master" as a branch.

  - Worse, if the other branch "bar" is instead called "master" (but
    again, the remote HEAD is not pointing to it), then we end up with a
    local unborn branch "master", which is not connected to the remote
    "master" (it shares no history, and there's no branch.* config).

Instead, we should try to use the remote's HEAD, even if its unborn, to
be consistent with the other cases.

Some notes on the implementation:

 - we don't emit any specific warning here, which is unlike the
   empty-repo case (which says "you appear to have cloned an empty
   reopsitory"). For non-bare clones, checkout() will issue a warning
   like:

     warning: remote HEAD refers to nonexistent ref, unable to checkout

   For a bare clone, it won't emit any warning at all (but will still
   set up HEAD appropriately). That's probably fine. There's no part of
   the operation we were unable to perform, so any "by the way, the
   remote HEAD wasn't there but we pointed our HEAD to it anyway"
   message would be purely informational. Though perhaps one could argue
   the same about the current "empty repository" message in a bare
   clone.

 - if the remote told us about its HEAD via the unborn extension, this
   is obviously the right thing to do. If they didn't, we'll fall back
   to our local default name. As the "unborn" extension was added in
   v2.31.0, we'd expect hosts which don't support it to become
   decreasingly common, and it may not be worth worrying too much about.
   But for the sake of completeness, here are some thoughts:

     - if the remote has a non-HEAD "master", we may still end up with a
       local "master" that isn't connected to it. This is because the
       "how to set local unborn HEAD" code is independent from the "did
       we find a remote HEAD we can checkout" code. This could be fixed,
       but I'm not sure it's worth caring too much about, since you'd
       have to really try hard to create such a situation.

     - if the remote has branches but doesn't tell us about its HEAD,
       we could pick one of those branches as our HEAD instead of
       whatever our local default is. This feels on-balance worse to me.
       While it might do the right thing in some cases (especially if
       there is only a single branch), it could certainly lead to
       surprising and unintuitive outcomes in others.

Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/clone.c        |  7 +++++--
 t/t5702-protocol-v2.sh | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/builtin/clone.c b/builtin/clone.c
index b7d3962c12..aa0729f62d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1298,9 +1298,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			if (!our_head_points_at)
 				die(_("Remote branch %s not found in upstream %s"),
 				    option_branch, remote_name);
-		}
-		else
+		} else {
 			our_head_points_at = remote_head_points_at;
+			if (!our_head_points_at)
+				setup_unborn_head(transport_ls_refs_options.unborn_head_target,
+						  reflog_msg.buf);
+		}
 	}
 	else {
 		if (option_branch)
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 00ce9aec23..822ca334c4 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -250,6 +250,44 @@ test_expect_success 'bare clone propagates empty default branch' '
 	grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD
 '
 
+test_expect_success 'clone propagates empty default branch from non-empty repo' '
+	test_when_finished "rm -rf file_empty_parent file_empty_child" &&
+
+	git init file_empty_parent &&
+	(
+		cd file_empty_parent &&
+		git checkout -b branchwithstuff &&
+		test_commit --no-tag stuff &&
+		git symbolic-ref HEAD refs/heads/mydefaultbranch
+	) &&
+
+	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+	git -c init.defaultBranch=main -c protocol.version=2 \
+		clone "file://$(pwd)/file_empty_parent" \
+		file_empty_child 2>stderr &&
+	grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD &&
+	grep "warning: remote HEAD refers to nonexistent ref" stderr
+'
+
+test_expect_success 'bare clone propagates empty default branch from non-empty repo' '
+	test_when_finished "rm -rf file_empty_parent file_empty_child.git" &&
+
+	git init file_empty_parent &&
+	(
+		cd file_empty_parent &&
+		git checkout -b branchwithstuff &&
+		test_commit --no-tag stuff &&
+		git symbolic-ref HEAD refs/heads/mydefaultbranch
+	) &&
+
+	GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+	git -c init.defaultBranch=main -c protocol.version=2 \
+		clone --bare "file://$(pwd)/file_empty_parent" \
+		file_empty_child.git 2>stderr &&
+	grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD &&
+	! grep "warning:" stderr
+'
+
 test_expect_success 'fetch with file:// using protocol v2' '
 	test_when_finished "rm -f log" &&
 
-- 
2.37.0.408.g2817302ee7

  parent reply	other threads:[~2022-07-06  8:03 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-06  7:57 [PATCH 0/3] cloning unborn HEAD when other branches are present Jeff King
2022-07-06  8:00 ` [PATCH 1/3] clone: drop extra newline from warning message Jeff King
2022-07-06  8:00 ` [PATCH 2/3] clone: factor out unborn head setup into its own function Jeff King
2022-07-06  8:03 ` Jeff King [this message]
2022-07-06 18:19   ` [PATCH 3/3] clone: propagate empty remote HEAD even with other branches Junio C Hamano
2022-07-06 22:01     ` Junio C Hamano
2022-07-07 17:40       ` Jeff King
2022-07-07 18:50         ` Junio C Hamano
2022-07-07 23:54           ` [PATCH v2 0/3] cloning unborn HEAD when other branches are present Jeff King
2022-07-07 23:54             ` [PATCH v2 1/3] clone: drop extra newline from warning message Jeff King
2022-07-07 23:57             ` [PATCH v2 2/3] clone: propagate empty remote HEAD even with other branches Jeff King
2022-07-08 15:41               ` Junio C Hamano
2022-07-08 16:08                 ` Jeff King
2022-07-07 23:59             ` [PATCH v2 3/3] clone: use remote branch if it matches default HEAD Jeff King
2022-07-08 16:28               ` Junio C Hamano
2022-07-08 19:30                 ` Jeff King
2022-07-08 20:33                   ` Junio C Hamano
2022-07-11  9:21                     ` [PATCH v2 4/3] clone: move unborn head creation to update_head() Jeff King
2022-07-11 20:36                       ` Junio C Hamano
2022-07-07 17:23     ` [PATCH 3/3] clone: propagate empty remote HEAD even with other branches Jeff King
2022-07-06 18:17 ` [PATCH 0/3] cloning unborn HEAD when other branches are present Jonathan Tan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YsVB2eOMp5guQfVr@coredump.intra.peff.net \
    --to=peff@peff.net \
    --cc=git@vger.kernel.org \
    --cc=jonathantanmy@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.