threads / patch / 29298

patchclone: allow detached checkout when --branch takes a tag

Subject: [PATCH] clone: allow detached checkout when --branch takes a tag

## tl;dr

68 messages between Jan 5, 2012 and Jan 24, 2012. Diffs are folded; open one to read it.

replies: 67people: 3as markdown or json

Nguyễn Thái Ngọc Duy· Jan 5, 2012, 13:49 UTC · lore

This allows you to do "git clone --branch=v1.7.8 git.git" and work right away from there. No big deal, just one more convenient step, I think. --branch taking a tag may be confusing though.

We can still have master in this case instead of detached HEAD, which may make more sense because we use --branch. I don't care much which way should be used.

Like? Dislike?
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   20 +++++++++++++++++++-
 1 files changed, 19 insertions(+), 1 deletions(-)
Show changes to builtin/clone.c +19 −1
diff --git a/builtin/clone.c b/builtin/clone.c
index 8f29912..97af4bd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -23,6 +23,7 @@
 #include "branch.h"
 #include "remote.h"
 #include "run-command.h"
+#include "tag.h"
 
 /*
  * Overall FIXMEs:
@@ -721,6 +722,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			strbuf_release(&head);
 
 			if (!our_head_points_at) {
+				strbuf_addstr(&head, "refs/tags/");
+				strbuf_addstr(&head, option_branch);
+				our_head_points_at =
+					find_ref_by_name(mapped_refs, head.buf);
+				strbuf_release(&head);
+			}
+
+			if (!our_head_points_at) {
 				warning(_("Remote branch %s not found in "
 					"upstream %s, using HEAD instead"),
 					option_branch, option_origin);
@@ -750,7 +759,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			      reflog_msg.buf);
 	}
 
-	if (our_head_points_at) {
+	if (our_head_points_at &&
+	    !prefixcmp(our_head_points_at->name, "refs/tags/")) {
+		const struct ref *ref = our_head_points_at;
+		struct object *o;
+
+		/* Detached HEAD */
+		o = deref_tag(parse_object(ref->old_sha1), NULL, 0);
+		update_ref(reflog_msg.buf, "HEAD", o->sha1, NULL,
+			   REF_NODEREF, DIE_ON_ERR);
+	} else if (our_head_points_at) {
 		/* Local default branch link */
 		create_symref("HEAD", our_head_points_at->name, NULL);
 		if (!option_bare) {
-- 
1.7.8.36.g69ee2
Jeff King· Jan 5, 2012, 14:18 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] clone: allow detached checkout when --branch takes a tag

On Thu, Jan 05, 2012 at 08:49:40PM +0700, Nguyen Thai Ngoc Duy wrote:
Show 9 quoted lines
> This allows you to do "git clone --branch=v1.7.8 git.git" and work
> right away from there. No big deal, just one more convenient step, I
> think. --branch taking a tag may be confusing though.
> 
> We can still have master in this case instead of detached HEAD, which
> may make more sense because we use --branch. I don't care much which
> way should be used.
> 
> Like? Dislike?

Seems like a reasonable goal to me. I agree that "--branch=v1.7.8" is a little confusing, but not the end of the world. If we were designing it from scratch, I might call it "--head" or "--checkout" or something to indicate that it is what we are putting in HEAD. But I don't know that it is worth renaming the option or adding a new option.

Show 12 quoted lines
> @@ -721,6 +722,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>  			strbuf_release(&head);
>  
>  			if (!our_head_points_at) {
> +				strbuf_addstr(&head, "refs/tags/");
> +				strbuf_addstr(&head, option_branch);
> +				our_head_points_at =
> +					find_ref_by_name(mapped_refs, head.buf);
> +				strbuf_release(&head);
> +			}
> +
> +			if (!our_head_points_at) {
Hmm. The context just above your patch that got snipped does this:
    strbuf_addstr(&head, src_ref_prefix);
    strbuf_addstr(&head, option_branch);
    our_head_points_at =
        find_ref_by_name(mapped_refs, head.buf);

where src_ref_prefix typically is "refs/heads/", and clearly you are meaning to do the same thing for tags. But the use of "src_ref_prefix" is interesting.

It is always "refs/heads/" unless we are cloning into a bare mirror, in which case it is "refs/". So with your patch in the non-mirror case, doing "--branch=foo" would try "refs/heads/foo" followed by "refs/tags/foo". Which makes sense. But in the mirror case, it will try "refs/foo" followed by "refs/tags/foo", which is kind of odd.

I wonder, though, if the original code makes any sense. By using "refs/", I would have to say "--branch=heads/foo", which is kind of weird and undocumented. I think it should probably always be "refs/heads/", no matter if we are mirroring or not.

Show 7 quoted lines
> @@ -750,7 +759,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>  			      reflog_msg.buf);
>  	}
>  
> -	if (our_head_points_at) {
> +	if (our_head_points_at &&
> +	    !prefixcmp(our_head_points_at->name, "refs/tags/")) {
I think I would prefer this check to be:
  prefixcmp(our_head_points_at->name, "refs/heads/")

which more closely matches the rules for what is allowed to go in HEAD as a symbolic ref. It's pretty hard to get something other than heads or tags, but you can do it with "git clone --bare --mirror --branch=foo/bar". I did argue above for doing away with that "feature", but I still think it future-proofs this section of code to handle anything.

Show 7 quoted lines
> +		const struct ref *ref = our_head_points_at;
> +		struct object *o;
> +
> +		/* Detached HEAD */
> +		o = deref_tag(parse_object(ref->old_sha1), NULL, 0);
> +		update_ref(reflog_msg.buf, "HEAD", o->sha1, NULL,
> +			   REF_NODEREF, DIE_ON_ERR);

It's unlikely, but deref_tag can return NULL, in which case this will segfault (ditto with parse_object, I think). I suspect that is a problem in lots of places, though. I wonder if deref_tag should simply die if we have a missing object (and we can add a _gently form for things like fsck which want to handle the error condition).

Also, any reason the "warn" flag to deref_tag should not be 1?
Other than those minor complaints, the patch looks good to me.
-Peff
Nguyen Thai Ngoc Duy· Jan 6, 2012, 11:09 UTC · re: Jeff King · lore

Re: [PATCH] clone: allow detached checkout when --branch takes a tag

2012/1/5 Jeff King <peff@peff.net>:
Show 34 quoted lines
>> @@ -721,6 +722,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>>                       strbuf_release(&head);
>>
>>                       if (!our_head_points_at) {
>> +                             strbuf_addstr(&head, "refs/tags/");
>> +                             strbuf_addstr(&head, option_branch);
>> +                             our_head_points_at =
>> +                                     find_ref_by_name(mapped_refs, head.buf);
>> +                             strbuf_release(&head);
>> +                     }
>> +
>> +                     if (!our_head_points_at) {
>
> Hmm. The context just above your patch that got snipped does this:
>
>    strbuf_addstr(&head, src_ref_prefix);
>    strbuf_addstr(&head, option_branch);
>    our_head_points_at =
>        find_ref_by_name(mapped_refs, head.buf);
>
> where src_ref_prefix typically is "refs/heads/", and clearly you are
> meaning to do the same thing for tags. But the use of "src_ref_prefix"
> is interesting.
>
> It is always "refs/heads/" unless we are cloning into a bare mirror, in
> which case it is "refs/". So with your patch in the non-mirror case,
> doing "--branch=foo" would try "refs/heads/foo" followed by
> "refs/tags/foo". Which makes sense. But in the mirror case, it will try
> "refs/foo" followed by "refs/tags/foo", which is kind of odd.
>
> I wonder, though, if the original code makes any sense. By using
> "refs/", I would have to say "--branch=heads/foo", which is kind of
> weird and undocumented. I think it should probably always be
> "refs/heads/", no matter if we are mirroring or not.

--branch should not be used with --mirror in my opinion. --branch changes HEAD so it's no longer an exact mirror.

-- 
Duy
Jeff King· Jan 6, 2012, 14:42 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH] clone: allow detached checkout when --branch takes a tag

On Fri, Jan 06, 2012 at 06:09:16PM +0700, Nguyen Thai Ngoc Duy wrote:
Show 7 quoted lines
> > I wonder, though, if the original code makes any sense. By using
> > "refs/", I would have to say "--branch=heads/foo", which is kind of
> > weird and undocumented. I think it should probably always be
> > "refs/heads/", no matter if we are mirroring or not.
> 
> --branch should not be used with --mirror in my opinion. --branch
> changes HEAD so it's no longer an exact mirror.

You could be making a repo that mirrors all of the refs, but has a different HEAD (e.g., the upstream has "development" as the main branch, but you want a local mirror with "production" as the HEAD).

I agree it's an unlikely combination (which is probably why nobody has complained about the weird behavior), but I don't see a particular reason to forbid it.

-Peff
Junio C Hamano· Jan 5, 2012, 16:22 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] clone: allow detached checkout when --branch takes a tag

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 7 quoted lines
> This allows you to do "git clone --branch=v1.7.8 git.git" and work
> right away from there. No big deal, just one more convenient step, I
> think. --branch taking a tag may be confusing though.
>
> We can still have master in this case instead of detached HEAD, which
> may make more sense because we use --branch. I don't care much which
> way should be used.

You clone a single lineage of the history, either shallowly or fully, either starting at the tip of one single branch or a named tag.

What is the expected use scenario of a resulting repository of this new feature? As this is creating a repository, not a tarball extract, you certainly would want the user to build further history in the resulting repository, and it would need a real branch at some point, preferably before any new commit is made. Which makes me think that the only reason we would use a detached HEAD would be because we cannot decide what name to give to that single branch and make it the responsibility of the user to run "git checkout -b $whatever" as the first thing.

I think the real cause of the above is because this patch and its previous companion patch conflate the meaning of the "--branch" option with the purpose of specifying which lineage of the history to copy. The option is described to name the local branch that is checked out, instead of using the the same name the remote's primary branch. But these patches abuse the option to name something different at the same time---the endpoint of the single lineage to be copied.

These two may often be the same, and use of "clone --branch=master" in such a case would mean that you want to name the local branch of the final checkout to be "master" _and_ the endpoint of the single lineage you are copying is also their "master".

But the "tag" extension proposed with this change is different.

You are specifying an endpoint of the single lineage with the option that is different from any of the branches at the origin, and because you used the "--branch" option for that purpose, you lost the way to specify the primary thing the option wanted to express: what the name of the resulting checkout should be.

Perhaps something like "clone --branch=master --$endpoint=v1.7.8" that says "I want a clone of the repository limited to a single lineage, whose history ends at the commit pointed by the v1.7.8 tag, and name the local checkout my master branch" be more appropriate?

Also, the user is likely to want to fetch and integrate from the origin with his own history. How should "git pull" and "git fetch" work in the resulting repository? What should the remote.origin.* look like?

Nguyen Thai Ngoc Duy· Jan 6, 2012, 07:35 UTC · re: Junio C Hamano · lore

Re: [PATCH] clone: allow detached checkout when --branch takes a tag

2012/1/5 Junio C Hamano <gitster@pobox.com>:
> Also, the user is likely to want to fetch and integrate from the origin
> with his own history. How should "git pull" and "git fetch" work in the
> resulting repository? What should the remote.origin.* look like?

fetch should behave the same regardless this patch. remote.origin.* looks exactly the same. Branch tracking is not set up so pull works half way.

Show 11 quoted lines
> You clone a single lineage of the history, either shallowly or fully,
> either starting at the tip of one single branch or a named tag.
>
> What is the expected use scenario of a resulting repository of this new
> feature? As this is creating a repository, not a tarball extract, you
> certainly would want the user to build further history in the resulting
> repository, and it would need a real branch at some point, preferably
> before any new commit is made. Which makes me think that the only reason
> we would use a detached HEAD would be because we cannot decide what name
> to give to that single branch and make it the responsibility of the user
> to run "git checkout -b $whatever" as the first thing.

We can still commit with detached HEAD but I guess it's not recommended. For a quick, throwaway repository, it's probably OK.

Show 25 quoted lines
> I think the real cause of the above is because this patch and its previous
> companion patch conflate the meaning of the "--branch" option with the
> purpose of specifying which lineage of the history to copy. The option is
> described to name the local branch that is checked out, instead of using
> the the same name the remote's primary branch. But these patches abuse the
> option to name something different at the same time---the endpoint of the
> single lineage to be copied.
>
> These two may often be the same, and use of "clone --branch=master" in
> such a case would mean that you want to name the local branch of the final
> checkout to be "master" _and_ the endpoint of the single lineage you are
> copying is also their "master".
>
> But the "tag" extension proposed with this change is different.
>
> You are specifying an endpoint of the single lineage with the option that
> is different from any of the branches at the origin, and because you used
> the "--branch" option for that purpose, you lost the way to specify the
> primary thing the option wanted to express: what the name of the resulting
> checkout should be.
>
> Perhaps something like "clone --branch=master --$endpoint=v1.7.8" that
> says "I want a clone of the repository limited to a single lineage, whose
> history ends at the commit pointed by the v1.7.8 tag, and name the local
> checkout my master branch" be more appropriate?

Branch naming is a local thing. Maybe we could get away with always name it "master" if fetched from a tag. Branch tracking is not set up in this case, so if they want to follow up with upstream, they have to do some more themselves (including renaming "master" to something else for less confusion with upstream "master"). New option is not easily discovered, while "git branch -r master foo" is quite easy.

-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 1/6] t5601: add &&

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t5601-clone.sh |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
Show changes to t/t5601-clone.sh +3 −3
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 87ee016..49821eb 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -9,9 +9,9 @@ test_expect_success setup '
 	rm -fr .git &&
 	test_create_repo src &&
 	(
-		cd src
-		>file
-		git add file
+		cd src &&
+		>file &&
+		git add file &&
 		git commit -m initial
 	)
 
-- 
1.7.8.36.g69ee2
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 2/6] clone: write detached HEAD in bare repositories

If we don't write, HEAD is still at refs/heads/master as initialized by init-db, which may or may not match remote's HEAD.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c  |    9 +++------
 t/t5601-clone.sh |   25 ++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 7 deletions(-)
Show changes to 2 files +27 −7

builtin/clone.c, t/t5601-clone.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 86db954..8dde1ea 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
-		if (!option_bare) {
-			update_ref(reflog_msg.buf, "HEAD",
-				   remote_head->old_sha1,
-				   NULL, REF_NODEREF, DIE_ON_ERR);
-			our_head_points_at = remote_head;
-		}
+		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+		our_head_points_at = remote_head;
 	} else {
 		/* Nothing to checkout out */
 		if (!option_no_checkout)
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 49821eb..e0b8db6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -12,7 +12,10 @@ test_expect_success setup '
 		cd src &&
 		>file &&
 		git add file &&
-		git commit -m initial
+		git commit -m initial &&
+		echo 1 >file &&
+		git add file &&
+		git commit -m updated
 	)
 
 '
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
 
 '
 
+test_expect_success 'clone --mirror with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --mirror src mirror.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --bare src bare.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
 test_expect_success 'clone --bare names the local repository <name>.git' '
 
 	git clone --bare src &&
-- 
1.7.8.36.g69ee2
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 3/6] clone: factor out checkout code

Read HEAD from disk instead of relying on local variable our_head_points_at, so that if earlier code fails to make HEAD properly, it'll be detected.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |  101 +++++++++++++++++++++++++++++++-----------------------
 1 files changed, 58 insertions(+), 43 deletions(-)
Show changes to builtin/clone.c +58 −43
diff --git a/builtin/clone.c b/builtin/clone.c
index 8dde1ea..100056d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static int checkout(void)
+{
+	unsigned char sha1[20];
+	char *head;
+	struct lock_file *lock_file;
+	struct unpack_trees_options opts;
+	struct tree *tree;
+	struct tree_desc t;
+	int err = 0, fd;
+
+	if (option_no_checkout)
+		return 0;
+
+	head = resolve_refdup("HEAD", sha1, 1, NULL);
+	if (!head) {
+		warning(_("remote HEAD refers to nonexistent ref, "
+			  "unable to checkout.\n"));
+		return 0;
+	}
+	if (strcmp(head, "HEAD")) {
+		if (prefixcmp(head, "refs/heads/"))
+			die(_("HEAD not found below refs/heads!"));
+	}
+	free(head);
+
+	/* We need to be in the new work tree for the checkout */
+	setup_work_tree();
+
+	lock_file = xcalloc(1, sizeof(struct lock_file));
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&opts, 0, sizeof opts);
+	opts.update = 1;
+	opts.merge = 1;
+	opts.fn = oneway_merge;
+	opts.verbose_update = (option_verbosity > 0);
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+
+	tree = parse_tree_indirect(sha1);
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	unpack_trees(1, &t, &opts);
+
+	if (write_cache(fd, active_cache, active_nr) ||
+	    commit_locked_index(lock_file))
+		die(_("unable to write new index file"));
+
+	err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+			sha1_to_hex(sha1), "1", NULL);
+
+	if (!err && option_recursive)
+		err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+	return err;
+}
+
 static int write_one_config(const char *key, const char *value, void *data)
 {
 	return git_config_set_multivar(key, value ? value : "true", "^$", 0);
@@ -722,13 +779,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
 			   NULL, REF_NODEREF, DIE_ON_ERR);
-		our_head_points_at = remote_head;
-	} else {
-		/* Nothing to checkout out */
-		if (!option_no_checkout)
-			warning(_("remote HEAD refers to nonexistent ref, "
-				"unable to checkout.\n"));
-		option_no_checkout = 1;
 	}
 
 	if (transport) {
@@ -736,42 +786,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		transport_disconnect(transport);
 	}
 
-	if (!option_no_checkout) {
-		struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-		struct unpack_trees_options opts;
-		struct tree *tree;
-		struct tree_desc t;
-		int fd;
-
-		/* We need to be in the new work tree for the checkout */
-		setup_work_tree();
-
-		fd = hold_locked_index(lock_file, 1);
-
-		memset(&opts, 0, sizeof opts);
-		opts.update = 1;
-		opts.merge = 1;
-		opts.fn = oneway_merge;
-		opts.verbose_update = (option_verbosity > 0);
-		opts.src_index = &the_index;
-		opts.dst_index = &the_index;
-
-		tree = parse_tree_indirect(our_head_points_at->old_sha1);
-		parse_tree(tree);
-		init_tree_desc(&t, tree->buffer, tree->size);
-		unpack_trees(1, &t, &opts);
-
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(lock_file))
-			die(_("unable to write new index file"));
-
-		err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-				sha1_to_hex(our_head_points_at->old_sha1), "1",
-				NULL);
-
-		if (!err && option_recursive)
-			err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-	}
+	err = checkout();
 
 	strbuf_release(&reflog_msg);
 	strbuf_release(&branch_top);
-- 
1.7.8.36.g69ee2
Junio C Hamano· Jan 10, 2012, 00:32 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 3/6] clone: factor out checkout code

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
> Read HEAD from disk instead of relying on local variable
> our_head_points_at, so that if earlier code fails to make HEAD
> properly, it'll be detected.

The end result might be more or less the same with your patch from the end-user's point of view, but "if earlier code fails", shouldn't you detect and diagnose it right there?

If you observe lack of "HEAD" in checkout(), you cannot tell if that was because the remote did not have anything usable in the first place, or because we knew where it should point at (and may have even attempted to create it) but somehow failed to make it point at it.

Nguyen Thai Ngoc Duy· Jan 10, 2012, 02:01 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 3/6] clone: factor out checkout code

2012/1/10 Junio C Hamano <gitster@pobox.com>:
Show 9 quoted lines
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> Read HEAD from disk instead of relying on local variable
>> our_head_points_at, so that if earlier code fails to make HEAD
>> properly, it'll be detected.
>
> The end result might be more or less the same with your patch from the
> end-user's point of view, but "if earlier code fails", shouldn't you
> detect and diagnose it right there?

Sure, but another fence does not harm. There's also one thing I missed in the commit message that it makes update head code and checkout code more independent. Update head code does not need to maintain our_head_points_at at the end for checkout anymore.

> If you observe lack of "HEAD" in checkout(), you cannot tell if that was
> because the remote did not have anything usable in the first place, or
> because we knew where it should point at (and may have even attempted to
> create it) but somehow failed to make it point at it.

The lack of HEAD probably won't happen because HEAD is created by default in init-db. This is mainly to catch invalid HEAD (like putting "refs/tags/something" in HEAD).

-- 
Duy
Junio C Hamano· Jan 10, 2012, 04:59 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH v2 3/6] clone: factor out checkout code

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 11 quoted lines
> 2012/1/10 Junio C Hamano <gitster@pobox.com>:
>>
>>> Read HEAD from disk instead of relying on local variable
>>> our_head_points_at, so that if earlier code fails to make HEAD
>>> properly, it'll be detected.
>>
>> The end result might be more or less the same with your patch from the
>> end-user's point of view, but "if earlier code fails", shouldn't you
>> detect and diagnose it right there?
>
> Sure, but another fence does not harm.

But that is not "another" fence but is the _only_ fence, as you do not check after running update_ref of "HEAD".

> There's also one thing I missed in the commit message that it makes
> update head code and checkout code more independent. Update head code
> does not need to maintain our_head_points_at at the end for checkout
> anymore.
I like that reasoning in general. The logic ought to be:
 - Learn what the remote has;
 - Combine it with --branch parameter, determine what local branch our
   head _should_ point at;
 - Make our head point at it, and check it out.

I wonder if we can somehow make the above logic more clear in the code. Perhaps the first two could be made into a single helper function "decide_local_branch()", and the third would be the "checkout()" function in your patch, updated to take "const char *" parameter or something?

> The lack of HEAD probably won't happen because HEAD is created by
> default in init-db. This is mainly to catch invalid HEAD (like putting
> "refs/tags/something" in HEAD).

Sorry; what I meant by "lack" was "... if earlier code fails to make HEAD properly" case.

Nguyen Thai Ngoc Duy· Jan 10, 2012, 05:57 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 3/6] clone: factor out checkout code

On Tue, Jan 10, 2012 at 11:59 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 18 quoted lines
>> There's also one thing I missed in the commit message that it makes
>> update head code and checkout code more independent. Update head code
>> does not need to maintain our_head_points_at at the end for checkout
>> anymore.
>
> I like that reasoning in general. The logic ought to be:
>
>  - Learn what the remote has;
>
>  - Combine it with --branch parameter, determine what local branch our
>   head _should_ point at;
>
>  - Make our head point at it, and check it out.
>
> I wonder if we can somehow make the above logic more clear in the
> code. Perhaps the first two could be made into a single helper function
> "decide_local_branch()", and the third would be the "checkout()" function
> in your patch, updated to take "const char *" parameter or something?

yeah, I split the first two into update_head() but dropped it for some reasons I don't remember. That would make the main function easier to follow. I'll look at it again.

-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 4/6] clone: --branch=<branch> always means refs/heads/<branch>

It does not make sense to look outside refs/heads for HEAD's target (src_ref_prefix can be set to "refs/" if --mirror is used) because ref code only allows symref in form refs/heads/...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)
Show changes to builtin/clone.c +3 −2
diff --git a/builtin/clone.c b/builtin/clone.c
index 100056d..914fd6b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -727,7 +727,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 		if (option_branch) {
 			struct strbuf head = STRBUF_INIT;
-			strbuf_addstr(&head, src_ref_prefix);
+			strbuf_addstr(&head, "refs/heads/");
 			strbuf_addstr(&head, option_branch);
 			our_head_points_at =
 				find_ref_by_name(mapped_refs, head.buf);
@@ -763,7 +763,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			      reflog_msg.buf);
 	}
 
-	if (our_head_points_at) {
+	if (our_head_points_at &&
+	    !prefixcmp(our_head_points_at->name, "refs/heads/")) {
 		/* Local default branch link */
 		create_symref("HEAD", our_head_points_at->name, NULL);
 		if (!option_bare) {
-- 
1.7.8.36.g69ee2
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 5/6] clone: allow --branch to take a tag

Because a tag ref cannot be put to HEAD, HEAD will become detached. This is consistent with "git checkout <tag>".

This is mostly useful in shallow clone, where it allows you to clone a tag in addtion to branches.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Although 'git clone --depth 1 --branch <tag>' still needs fixups on
 top. I'll do that later.
 Documentation/git-clone.txt |    5 +++--
 builtin/clone.c             |   14 ++++++++++++++
 t/t5601-clone.sh            |    9 +++++++++
 3 files changed, 26 insertions(+), 2 deletions(-)
Show changes to 3 files +26 −2

Documentation/git-clone.txt, builtin/clone.c, t/t5601-clone.sh

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 4b8b26b..db2b29c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -146,8 +146,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
 	Instead of pointing the newly created HEAD to the branch pointed
 	to by the cloned repository's HEAD, point to `<name>` branch
-	instead. In a non-bare repository, this is the branch that will
-	be checked out.
+	instead. `--branch` can also take tags and treat them like
+	detached HEAD. In a non-bare repository, this is the branch
+	that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
diff --git a/builtin/clone.c b/builtin/clone.c
index 914fd6b..0d4b9ab 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -734,6 +734,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			strbuf_release(&head);
 
 			if (!our_head_points_at) {
+				strbuf_addstr(&head, "refs/tags/");
+				strbuf_addstr(&head, option_branch);
+				our_head_points_at =
+					find_ref_by_name(mapped_refs, head.buf);
+				strbuf_release(&head);
+			}
+
+			if (!our_head_points_at) {
 				warning(_("Remote branch %s not found in "
 					"upstream %s, using HEAD instead"),
 					option_branch, option_origin);
@@ -776,6 +784,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			install_branch_config(0, head, option_origin,
 					      our_head_points_at->name);
 		}
+	} else if (our_head_points_at) {
+		const struct ref *ref = our_head_points_at;
+		struct commit *c = lookup_commit_reference(ref->old_sha1);
+		/* Source had detached HEAD pointing somewhere. */
+		update_ref(reflog_msg.buf, "HEAD", c->object.sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e0b8db6..67869b4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
 	grep /src/\\.git/objects target-10/objects/info/alternates
 '
 
+test_expect_success 'clone checking out a tag' '
+	git clone --branch=some-tag src dst.tag &&
+	GIT_DIR=src/.git git rev-parse some-tag >expected &&
+	test_cmp expected dst.tag/.git/HEAD &&
+	GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+	echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+	test_cmp fetch.expected fetch.actual
+'
+
 test_done
-- 
1.7.8.36.g69ee2
Nguyễn Thái Ngọc Duy· Jan 8, 2012, 11:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 6/6] clone: print advice on checking out detached HEAD

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c           |   14 ++++++++++++++
 advice.h           |    1 +
 builtin/checkout.c |   16 +---------------
 builtin/clone.c    |    5 ++++-
 4 files changed, 20 insertions(+), 16 deletions(-)
Show changes to 4 files +20 −16

advice.c, advice.h, builtin/checkout.c, builtin/clone.c

diff --git a/advice.c b/advice.c
index e02e632..3e1a145 100644
--- a/advice.c
+++ b/advice.c
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)
 	error_resolve_conflict(me);
 	die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+	const char fmt[] =
+	"Note: checking out '%s'.\n\n"
+	"You are in 'detached HEAD' state. You can look around, make experimental\n"
+	"changes and commit them, and you can discard any commits you make in this\n"
+	"state without impacting any branches by performing another checkout.\n\n"
+	"If you want to create a new branch to retain commits you create, you may\n"
+	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
+	"  git checkout -b new_branch_name\n\n";
+
+	fprintf(stderr, fmt, new_name);
+}
diff --git a/advice.h b/advice.h
index e5d0af7..7bda45b 100644
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f1984d9..5bf96ba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-	const char fmt[] =
-	"Note: checking out '%s'.\n\n"
-	"You are in 'detached HEAD' state. You can look around, make experimental\n"
-	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
-	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b new_branch_name\n\n";
-
-	fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -575,7 +561,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old->path && advice_detached_head)
-				detach_advice(old->path, new->name);
+				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
 		}
 	} else if (new->path) {	/* Switch branches. */
diff --git a/builtin/clone.c b/builtin/clone.c
index 0d4b9ab..52596db 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -467,7 +467,10 @@ static int checkout(void)
 			  "unable to checkout.\n"));
 		return 0;
 	}
-	if (strcmp(head, "HEAD")) {
+	if (!strcmp(head, "HEAD")) {
+		if (advice_detached_head)
+			detach_advice(sha1_to_hex(sha1));
+	} else {
 		if (prefixcmp(head, "refs/heads/"))
 			die(_("HEAD not found below refs/heads!"));
 	}
-- 
1.7.8.36.g69ee2
Junio C Hamano· Jan 10, 2012, 00:36 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 6/6] clone: print advice on checking out detached HEAD

This patch makes 100% sense _if_ we let clone result in a repository with a detached HEAD, which I am not sure if it is a good idea, or if it is better to fail the attempt to clone to give incentive to the owner of the remote repository to fix it.

Nguyen Thai Ngoc Duy· Jan 10, 2012, 01:54 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 6/6] clone: print advice on checking out detached HEAD

2012/1/10 Junio C Hamano <gitster@pobox.com>:
> This patch makes 100% sense _if_ we let clone result in a repository with
> a detached HEAD, which I am not sure if it is a good idea, or if it is
> better to fail the attempt to clone to give incentive to the owner of the
> remote repository to fix it.

Then a hostile remote can stop users from cloning his repository by detaching HEAD? That's not nice. On the other hand, if specifying --branch=<wrong-branch> leads to detached case, then we should probably refuse to clone. But that should happen before transferring the pack.

-- 
Duy
Junio C Hamano· Jan 10, 2012, 04:49 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH v2 6/6] clone: print advice on checking out detached HEAD

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 8 quoted lines
> 2012/1/10 Junio C Hamano <gitster@pobox.com>:
>> This patch makes 100% sense _if_ we let clone result in a repository with
>> a detached HEAD, which I am not sure if it is a good idea, or if it is
>> better to fail the attempt to clone to give incentive to the owner of the
>> remote repository to fix it.
>
> Then a hostile remote can stop users from cloning his repository by
> detaching HEAD? That's not nice.
That's crazy talk. Why does anybody from a hostile remote to begin with?
> On the other hand, if specifying --branch=<wrong-branch> leads to
> detached case, then we should probably refuse to clone.

If you mean "nonexistent" by "wrong", yeah, I agree, as we do not know what the user asked us to pull down in that case.

Nguyen Thai Ngoc Duy· Jan 10, 2012, 05:54 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 6/6] clone: print advice on checking out detached HEAD

On Tue, Jan 10, 2012 at 11:49 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>
>> 2012/1/10 Junio C Hamano <gitster@pobox.com>:
>>> This patch makes 100% sense _if_ we let clone result in a repository with
>>> a detached HEAD, which I am not sure if it is a good idea, or if it is
>>> better to fail the attempt to clone to give incentive to the owner of the
>>> remote repository to fix it.
>>
>> Then a hostile remote can stop users from cloning his repository by
>> detaching HEAD? That's not nice.
>
> That's crazy talk. Why does anybody from a hostile remote to begin with?

The point is, why punish client for remote's problems? If I have to talk to upstream and wait for them to fix their repository, I might as well give up cloning and move on. It's OK to annoy users to the point that they ask upstream for a fix, but we should not disallow clone in that case.

-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 00/10] nd/clone-detached

Compare to v2, this round does more refactoring, which makes cmd_clone() looks easier to follow in the end, in my opinion.

There's also 7/10 that refuses --branch=<nonexistent>. I don't know if I react too strong. The current behavior is fall back to remote's HEAD (and detached HEAD if remote's HEAD is detached). Maybe we should only refuse it when it leads to detached HEAD and let it fall back to remote's HEAD otherwise.

The last two patches remain debatable. If we disallow detached HEAD from new clones, perhaps we could put <tag>^{commit} to refs/heads/master then drop the last patch. t3501.6, t5527.2, t5707.5, t7406.29 likes to have detached HEAD, but those can be fixed.

Nguyễn Thái Ngọc Duy (10):
  t5601: add missing && cascade
  clone: write detached HEAD in bare repositories
  clone: factor out checkout code
  clone: factor out HEAD update code
  clone: factor out remote ref writing
  clone: delay cloning until after remote HEAD checking
  clone: --branch=<branch> always means refs/heads/<branch>
  clone: refuse to clone if --branch points to bogus ref
  clone: allow --branch to take a tag
  clone: print advice on checking out detached HEAD
 Documentation/git-clone.txt |    5 +-
 advice.c                    |   14 +++
 advice.h                    |    1 +
 builtin/checkout.c          |   16 +---
 builtin/clone.c             |  252 +++++++++++++++++++++++++------------------
 t/t5601-clone.sh            |   40 ++++++-
 t/t5706-clone-branch.sh     |    8 +-
 transport.c                 |    5 +-
 8 files changed, 207 insertions(+), 134 deletions(-)
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 00/10] nd/clone-detached

Some comment updates after discussion and squash in the fixup patch. The code is exactly the same as nd/clone-detached in pu.

Nguyễn Thái Ngọc Duy (10):
  t5601: add missing && cascade
  clone: write detached HEAD in bare repositories
  clone: factor out checkout code
  clone: factor out HEAD update code
  clone: factor out remote ref writing
  clone: delay cloning until after remote HEAD checking
  clone: --branch=<branch> always means refs/heads/<branch>
  clone: refuse to clone if --branch points to bogus ref
  clone: allow --branch to take a tag
  clone: print advice on checking out detached HEAD
 Documentation/git-clone.txt |    5 +-
 advice.c                    |   14 +++
 advice.h                    |    1 +
 builtin/checkout.c          |   16 +---
 builtin/clone.c             |  256 +++++++++++++++++++++++++------------------
 t/t5601-clone.sh            |   40 ++++++-
 t/t5706-clone-branch.sh     |    8 +-
 transport.c                 |    5 +-
 8 files changed, 211 insertions(+), 134 deletions(-)
-- 
1.7.3.1.256.g2539c.dirty
Junio C Hamano· Jan 13, 2012, 19:52 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 00/10] nd/clone-detached

Thanks, replaced (and updated comment strings read much better).

There were some conlicts I had to resolve while merging this to 'pu'. I would appreciate it if you can eyeball it to make sure I didn't make silly mistakes there.

Thanks.
Nguyen Thai Ngoc Duy· Jan 14, 2012, 04:48 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 00/10] nd/clone-detached

2012/1/14 Junio C Hamano <gitster@pobox.com>:
Show 5 quoted lines
> Thanks, replaced (and updated comment strings read much better).
>
> There were some conlicts I had to resolve while merging this to 'pu'.
> I would appreciate it if you can eyeball it to make sure I didn't make
> silly mistakes there.

Right, the conflict with nd/clone-single-branch. I kept thinking there would not be conflict because clone-single-branch's big change was in wanted_peer_refs() and missed write_followtags() call. The merge looks good.

-- 
Duy
Junio C Hamano· Jan 14, 2012, 06:53 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH v4 00/10] nd/clone-detached

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 11 quoted lines
> 2012/1/14 Junio C Hamano <gitster@pobox.com>:
>> Thanks, replaced (and updated comment strings read much better).
>>
>> There were some conlicts I had to resolve while merging this to 'pu'.
>> I would appreciate it if you can eyeball it to make sure I didn't make
>> silly mistakes there.
>
> Right, the conflict with nd/clone-single-branch. I kept thinking there
> would not be conflict because clone-single-branch's big change was in
> wanted_peer_refs() and missed write_followtags() call. The merge looks
> good.
Hmm, 'pu' seems to fail its selftest with this merge present, though.
Nguyen Thai Ngoc Duy· Jan 14, 2012, 07:40 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 00/10] nd/clone-detached

On Fri, Jan 13, 2012 at 10:53:11PM -0800, Junio C Hamano wrote:
Show 15 quoted lines
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
> 
> > 2012/1/14 Junio C Hamano <gitster@pobox.com>:
> >> Thanks, replaced (and updated comment strings read much better).
> >>
> >> There were some conlicts I had to resolve while merging this to 'pu'.
> >> I would appreciate it if you can eyeball it to make sure I didn't make
> >> silly mistakes there.
> >
> > Right, the conflict with nd/clone-single-branch. I kept thinking there
> > would not be conflict because clone-single-branch's big change was in
> > wanted_peer_refs() and missed write_followtags() call. The merge looks
> > good.
> 
> Hmm, 'pu' seems to fail its selftest with this merge present, though.

The commit "refuse to clone if --branch points to bogus ref" from this series changes clone's behavior that t5500.31, which is added in nd/clone-single-branch, relies on. This makes "make test" pass for me on pu:

-- 8< --
Show changes to t/t5500-fetch-pack.sh +1 −5
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 7e85c71..c4e675f 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -283,10 +283,7 @@ test_expect_success 'clone shallow object count' '
 '
 
 test_expect_success 'clone shallow with nonexistent --branch' '
-	git clone --depth 1 --branch Z "file://$(pwd)/." shallow4 &&
-	GIT_DIR=shallow4/.git git rev-parse HEAD >actual &&
-	git rev-parse HEAD >expected &&
-	test_cmp expected actual
+	test_must_fail git clone --depth 1 --branch Z "file://$(pwd)/." shallow4
 '
 
 test_expect_success 'clone shallow with detached HEAD' '
-- 8< --

I'd rather remove the test, but removing something in a merge does not
sound wise.

Another cleaner approach is to combine the two clone series into
one. If you want to go this way, pick one as base, I'll rebase the
other on top and resend.
-- 
Duy
Junio C Hamano· Jan 15, 2012, 02:34 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH v4 00/10] nd/clone-detached

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 5 quoted lines
>> Hmm, 'pu' seems to fail its selftest with this merge present, though.
>
> The commit "refuse to clone if --branch points to bogus ref" from this
> series changes clone's behavior that t5500.31, which is added in
> nd/clone-single-branch, relies on.
Ahh, of course.
> Another cleaner approach is to combine the two clone series into
> one.

I think "--single-branch" is much more relevant compared to cloning from a repository with some funkiness like detached HEAD which is more about a theoretical fun exercise, so let's rebuild this on top of the other one.

Thanks.
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 00/10] nd/clone-detached rebase onto nd/clone-single-branch

There are a few more changes than just a simple rebase so that "git clone --depth=1 --branch=tag" also works.

Nguyễn Thái Ngọc Duy (10):
  t5601: add missing && cascade
  clone: write detached HEAD in bare repositories
  clone: factor out checkout code
  clone: factor out HEAD update code
  clone: factor out remote ref writing
  clone: delay cloning until after remote HEAD checking
  clone: --branch=<branch> always means refs/heads/<branch>
  clone: refuse to clone if --branch points to bogus ref
  clone: allow --branch to take a tag
  clone: print advice on checking out detached HEAD
 Documentation/git-clone.txt |    5 +-
 advice.c                    |   14 ++
 advice.h                    |    1 +
 builtin/checkout.c          |   16 +--
 builtin/clone.c             |  297 +++++++++++++++++++++++++------------------
 t/t5500-fetch-pack.sh       |   22 ++-
 t/t5601-clone.sh            |   40 +++++-
 t/t5706-clone-branch.sh     |    8 +-
 transport.c                 |    5 +-
 9 files changed, 249 insertions(+), 159 deletions(-)
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 01/10] t5601: add missing && cascade

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t5601-clone.sh |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
Show changes to t/t5601-clone.sh +3 −3
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 87ee016..49821eb 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -9,9 +9,9 @@ test_expect_success setup '
 	rm -fr .git &&
 	test_create_repo src &&
 	(
-		cd src
-		>file
-		git add file
+		cd src &&
+		>file &&
+		git add file &&
 		git commit -m initial
 	)
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 02/10] clone: write detached HEAD in bare repositories

If we don't write, HEAD is still at refs/heads/master as initialized by init-db, which may or may not match remote's HEAD.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c  |    9 +++------
 t/t5601-clone.sh |   25 ++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 7 deletions(-)
Show changes to 2 files +27 −7

builtin/clone.c, t/t5601-clone.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 9dcc5fe..91862f7 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -764,12 +764,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
-		if (!option_bare) {
-			update_ref(reflog_msg.buf, "HEAD",
-				   remote_head->old_sha1,
-				   NULL, REF_NODEREF, DIE_ON_ERR);
-			our_head_points_at = remote_head;
-		}
+		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+		our_head_points_at = remote_head;
 	} else {
 		/* Nothing to checkout out */
 		if (!option_no_checkout)
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 49821eb..e0b8db6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -12,7 +12,10 @@ test_expect_success setup '
 		cd src &&
 		>file &&
 		git add file &&
-		git commit -m initial
+		git commit -m initial &&
+		echo 1 >file &&
+		git add file &&
+		git commit -m updated
 	)
 
 '
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
 
 '
 
+test_expect_success 'clone --mirror with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --mirror src mirror.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --bare src bare.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
 test_expect_success 'clone --bare names the local repository <name>.git' '
 
 	git clone --bare src &&
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 03/10] clone: factor out checkout code

Read HEAD from disk instead of relying on local variable our_head_points_at, so that if earlier code fails to make HEAD properly, it'll be detected.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |  101 +++++++++++++++++++++++++++++++-----------------------
 1 files changed, 58 insertions(+), 43 deletions(-)
Show changes to builtin/clone.c +58 −43
diff --git a/builtin/clone.c b/builtin/clone.c
index 91862f7..98e3542 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -486,6 +486,63 @@ static void write_followtags(const struct ref *refs, const char *msg)
 	}
 }
 
+static int checkout(void)
+{
+	unsigned char sha1[20];
+	char *head;
+	struct lock_file *lock_file;
+	struct unpack_trees_options opts;
+	struct tree *tree;
+	struct tree_desc t;
+	int err = 0, fd;
+
+	if (option_no_checkout)
+		return 0;
+
+	head = resolve_refdup("HEAD", sha1, 1, NULL);
+	if (!head) {
+		warning(_("remote HEAD refers to nonexistent ref, "
+			  "unable to checkout.\n"));
+		return 0;
+	}
+	if (strcmp(head, "HEAD")) {
+		if (prefixcmp(head, "refs/heads/"))
+			die(_("HEAD not found below refs/heads!"));
+	}
+	free(head);
+
+	/* We need to be in the new work tree for the checkout */
+	setup_work_tree();
+
+	lock_file = xcalloc(1, sizeof(struct lock_file));
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&opts, 0, sizeof opts);
+	opts.update = 1;
+	opts.merge = 1;
+	opts.fn = oneway_merge;
+	opts.verbose_update = (option_verbosity > 0);
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+
+	tree = parse_tree_indirect(sha1);
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	unpack_trees(1, &t, &opts);
+
+	if (write_cache(fd, active_cache, active_nr) ||
+	    commit_locked_index(lock_file))
+		die(_("unable to write new index file"));
+
+	err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+			sha1_to_hex(sha1), "1", NULL);
+
+	if (!err && option_recursive)
+		err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+	return err;
+}
+
 static int write_one_config(const char *key, const char *value, void *data)
 {
 	return git_config_set_multivar(key, value ? value : "true", "^$", 0);
@@ -766,13 +823,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
 			   NULL, REF_NODEREF, DIE_ON_ERR);
-		our_head_points_at = remote_head;
-	} else {
-		/* Nothing to checkout out */
-		if (!option_no_checkout)
-			warning(_("remote HEAD refers to nonexistent ref, "
-				"unable to checkout.\n"));
-		option_no_checkout = 1;
 	}
 
 	if (transport) {
@@ -780,42 +830,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		transport_disconnect(transport);
 	}
 
-	if (!option_no_checkout) {
-		struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-		struct unpack_trees_options opts;
-		struct tree *tree;
-		struct tree_desc t;
-		int fd;
-
-		/* We need to be in the new work tree for the checkout */
-		setup_work_tree();
-
-		fd = hold_locked_index(lock_file, 1);
-
-		memset(&opts, 0, sizeof opts);
-		opts.update = 1;
-		opts.merge = 1;
-		opts.fn = oneway_merge;
-		opts.verbose_update = (option_verbosity > 0);
-		opts.src_index = &the_index;
-		opts.dst_index = &the_index;
-
-		tree = parse_tree_indirect(our_head_points_at->old_sha1);
-		parse_tree(tree);
-		init_tree_desc(&t, tree->buffer, tree->size);
-		unpack_trees(1, &t, &opts);
-
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(lock_file))
-			die(_("unable to write new index file"));
-
-		err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-				sha1_to_hex(our_head_points_at->old_sha1), "1",
-				NULL);
-
-		if (!err && option_recursive)
-			err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-	}
+	err = checkout();
 
 	strbuf_release(&reflog_msg);
 	strbuf_release(&branch_top);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 04/10] clone: factor out HEAD update code

While at it, update the comment at "if (remote_head)"
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   41 ++++++++++++++++++++++++-----------------
 1 files changed, 24 insertions(+), 17 deletions(-)
Show changes to builtin/clone.c +24 −17
diff --git a/builtin/clone.c b/builtin/clone.c
index 98e3542..3b68014 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -486,6 +486,29 @@ static void write_followtags(const struct ref *refs, const char *msg)
 	}
 }
 
+static void update_head(const struct ref *our, const struct ref *remote,
+			const char *msg)
+{
+	if (our) {
+		/* Local default branch link */
+		create_symref("HEAD", our->name, NULL);
+		if (!option_bare) {
+			const char *head = skip_prefix(our->name, "refs/heads/");
+			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+			install_branch_config(0, head, option_origin, our->name);
+		}
+	} else if (remote) {
+		/*
+		 * We know remote HEAD points to a non-branch, or
+		 * HEAD points to a branch but we don't know which one, or
+		 * we asked for a specific branch but it did not exist.
+		 * Detach HEAD in all these cases.
+		 */
+		update_ref(msg, "HEAD", remote->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+	}
+}
+
 static int checkout(void)
 {
 	unsigned char sha1[20];
@@ -807,23 +830,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			      reflog_msg.buf);
 	}
 
-	if (our_head_points_at) {
-		/* Local default branch link */
-		create_symref("HEAD", our_head_points_at->name, NULL);
-		if (!option_bare) {
-			const char *head = skip_prefix(our_head_points_at->name,
-						       "refs/heads/");
-			update_ref(reflog_msg.buf, "HEAD",
-				   our_head_points_at->old_sha1,
-				   NULL, 0, DIE_ON_ERR);
-			install_branch_config(0, head, option_origin,
-					      our_head_points_at->name);
-		}
-	} else if (remote_head) {
-		/* Source had detached HEAD pointing somewhere. */
-		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
-			   NULL, REF_NODEREF, DIE_ON_ERR);
-	}
+	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
 	if (transport) {
 		transport_unlock_pack(transport);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 05/10] clone: factor out remote ref writing

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   39 +++++++++++++++++++++++++--------------
 1 files changed, 25 insertions(+), 14 deletions(-)
Show changes to builtin/clone.c +25 −14
diff --git a/builtin/clone.c b/builtin/clone.c
index 3b68014..2733fa4 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -486,6 +486,29 @@ static void write_followtags(const struct ref *refs, const char *msg)
 	}
 }
 
+static void update_remote_refs(const struct ref *refs,
+			       const struct ref *mapped_refs,
+			       const struct ref *remote_head_points_at,
+			       const char *branch_top,
+			       const char *msg)
+{
+	if (refs) {
+		clear_extra_refs();
+		write_remote_refs(mapped_refs);
+		if (option_single_branch)
+			write_followtags(refs, msg);
+	}
+
+	if (remote_head_points_at && !option_bare) {
+		struct strbuf head_ref = STRBUF_INIT;
+		strbuf_addstr(&head_ref, branch_top);
+		strbuf_addstr(&head_ref, "HEAD");
+		create_symref(head_ref.buf,
+			      remote_head_points_at->peer_ref->name,
+			      msg);
+	}
+}
+
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
@@ -782,12 +805,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	if (refs) {
-		clear_extra_refs();
-
-		write_remote_refs(mapped_refs);
-		if (option_single_branch)
-			write_followtags(refs, reflog_msg.buf);
-
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
 			guess_remote_head(remote_head, mapped_refs, 0);
@@ -821,14 +838,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
-	if (remote_head_points_at && !option_bare) {
-		struct strbuf head_ref = STRBUF_INIT;
-		strbuf_addstr(&head_ref, branch_top.buf);
-		strbuf_addstr(&head_ref, "HEAD");
-		create_symref(head_ref.buf,
-			      remote_head_points_at->peer_ref->name,
-			      reflog_msg.buf);
-	}
+	update_remote_refs(refs, mapped_refs, remote_head_points_at,
+			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 06/10] clone: delay cloning until after remote HEAD checking

This gives us an opportunity to abort the command during remote HEAD check without wasting much bandwidth.

Cloning with remote-helper remains before the check because the remote helper updates mapped_refs, which is necessary for remote ref checks. foreign_vcs field is used to indicate the transport is handled by remote helper.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   54 +++++++++++++++++++++++++++---------------------------
 transport.c     |    5 ++++-
 2 files changed, 31 insertions(+), 28 deletions(-)
Show changes to 2 files +31 −28

builtin/clone.c, transport.c

diff --git a/builtin/clone.c b/builtin/clone.c
index 2733fa4..a1fbb3b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -364,13 +364,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	closedir(dir);
 }
 
-static const struct ref *clone_local(const char *src_repo,
-				     const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	const struct ref *ret;
-	struct remote *remote;
-	struct transport *transport;
-
 	if (option_shared) {
 		struct strbuf alt = STRBUF_INIT;
 		strbuf_addf(&alt, "%s/objects", src_repo);
@@ -386,13 +381,8 @@ static const struct ref *clone_local(const char *src_repo,
 		strbuf_release(&dest);
 	}
 
-	remote = remote_get(src_repo);
-	transport = transport_get(remote, src_repo);
-	ret = transport_get_remote_refs(transport);
-	transport_disconnect(transport);
 	if (0 <= option_verbosity)
 		printf(_("done.\n"));
-	return ret;
 }
 
 static const char *junk_work_tree;
@@ -619,6 +609,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
 	struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
 	struct transport *transport = NULL;
+	struct remote *remote;
 	int err = 0;
 
 	struct refspec *refspec;
@@ -773,13 +764,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 	strbuf_reset(&value);
 
-	if (is_local) {
-		refs = clone_local(path, git_dir);
-		mapped_refs = wanted_peer_refs(refs, refspec);
-	} else {
-		struct remote *remote = remote_get(option_origin);
-		transport = transport_get(remote, remote->url[0]);
+	remote = remote_get(option_origin);
+	transport = transport_get(remote, remote->url[0]);
 
+	if (!is_local) {
 		if (!transport->get_refs_list || !transport->fetch)
 			die(_("Don't know how to clone %s"), transport->url);
 
@@ -796,14 +784,23 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		if (option_upload_pack)
 			transport_set_option(transport, TRANS_OPT_UPLOADPACK,
 					     option_upload_pack);
-
-		refs = transport_get_remote_refs(transport);
-		if (refs) {
-			mapped_refs = wanted_peer_refs(refs, refspec);
-			transport_fetch_refs(transport, mapped_refs);
-		}
 	}
 
+	refs = transport_get_remote_refs(transport);
+	mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
+
+	/*
+	 * mapped_refs may be updated if transport-helper is used so
+	 * we need fetch it early because remote_head code below
+	 * relies on it.
+	 *
+	 * for normal clones, transport_get_remote_refs() should
+	 * return reliable ref set, we can delay cloning until after
+	 * remote HEAD check.
+	 */
+	if (!is_local && remote->foreign_vcs && refs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	if (refs) {
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
@@ -838,15 +835,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	if (is_local)
+		clone_local(path, git_dir);
+	else if (refs && !remote->foreign_vcs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	update_remote_refs(refs, mapped_refs, remote_head_points_at,
 			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-	if (transport) {
-		transport_unlock_pack(transport);
-		transport_disconnect(transport);
-	}
+	transport_unlock_pack(transport);
+	transport_disconnect(transport);
 
 	err = checkout();
 
diff --git a/transport.c b/transport.c
index a99b7c9..4366639 100644
--- a/transport.c
+++ b/transport.c
@@ -895,8 +895,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
 
 		while (is_urlschemechar(p == url, *p))
 			p++;
-		if (!prefixcmp(p, "::"))
+		if (!prefixcmp(p, "::")) {
 			helper = xstrndup(url, p - url);
+			remote->foreign_vcs = helper;
+		}
 	}
 
 	if (helper) {
@@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
 		char *handler = xmalloc(len + 1);
 		handler[len] = 0;
 		strncpy(handler, url, len);
+		remote->foreign_vcs = handler;
 		transport_helper_init(ret, handler);
 	}
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 07/10] clone: --branch=<branch> always means refs/heads/<branch>

It does not make sense to look outside refs/heads for HEAD's target (src_ref_prefix can be set to "refs/" if --mirror is used) because ref code only allows symref in form refs/heads/...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   30 ++++++++++++++++--------------
 1 files changed, 16 insertions(+), 14 deletions(-)
Show changes to builtin/clone.c +16 −14
diff --git a/builtin/clone.c b/builtin/clone.c
index a1fbb3b..253a794 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -48,7 +48,6 @@ static int option_verbosity;
 static int option_progress;
 static struct string_list option_config;
 static struct string_list option_reference;
-static const char *src_ref_prefix = "refs/heads/";
 
 static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
 {
@@ -413,6 +412,17 @@ static void remove_junk_on_signal(int signo)
 	raise(signo);
 }
 
+static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
+{
+	struct ref *ref;
+	struct strbuf head = STRBUF_INIT;
+	strbuf_addstr(&head, "refs/heads/");
+	strbuf_addstr(&head, branch);
+	ref = find_ref_by_name(refs, head.buf);
+	strbuf_release(&head);
+	return ref;
+}
+
 static struct ref *wanted_peer_refs(const struct ref *refs,
 		struct refspec *refspec)
 {
@@ -425,13 +435,8 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 
 		if (!option_branch)
 			remote_head = guess_remote_head(head, refs, 0);
-		else {
-			struct strbuf sb = STRBUF_INIT;
-			strbuf_addstr(&sb, src_ref_prefix);
-			strbuf_addstr(&sb, option_branch);
-			remote_head = find_ref_by_name(refs, sb.buf);
-			strbuf_release(&sb);
-		}
+		else
+			remote_head = find_remote_branch(refs, option_branch);
 
 		if (!remote_head && option_branch)
 			warning(_("Could not find remote branch %s to clone."),
@@ -502,7 +507,7 @@ static void update_remote_refs(const struct ref *refs,
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
-	if (our) {
+	if (our && !prefixcmp(our->name, "refs/heads/")) {
 		/* Local default branch link */
 		create_symref("HEAD", our->name, NULL);
 		if (!option_bare) {
@@ -609,6 +614,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
 	struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
 	struct transport *transport = NULL;
+	const char *src_ref_prefix = "refs/heads/";
 	struct remote *remote;
 	int err = 0;
 
@@ -807,12 +813,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			guess_remote_head(remote_head, mapped_refs, 0);
 
 		if (option_branch) {
-			struct strbuf head = STRBUF_INIT;
-			strbuf_addstr(&head, src_ref_prefix);
-			strbuf_addstr(&head, option_branch);
 			our_head_points_at =
-				find_ref_by_name(mapped_refs, head.buf);
-			strbuf_release(&head);
+				find_remote_branch(mapped_refs, option_branch);
 
 			if (!our_head_points_at) {
 				warning(_("Remote branch %s not found in "
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 08/10] clone: refuse to clone if --branch points to bogus ref

It's possible that users make a typo in the branch name. Stop and let users recheck. Falling back to remote's HEAD is not documented any way.

Except when using remote helper, the pack has not been transferred at this stage yet so we don't waste much bandwidth.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c         |   12 ++++--------
 t/t5500-fetch-pack.sh   |    7 -------
 t/t5706-clone-branch.sh |    8 ++------
 3 files changed, 6 insertions(+), 21 deletions(-)
Show changes to 3 files +6 −21

builtin/clone.c, t/t5500-fetch-pack.sh, t/t5706-clone-branch.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 253a794..3cfedb3 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -518,8 +518,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
 	} else if (remote) {
 		/*
 		 * We know remote HEAD points to a non-branch, or
-		 * HEAD points to a branch but we don't know which one, or
-		 * we asked for a specific branch but it did not exist.
+		 * HEAD points to a branch but we don't know which one.
 		 * Detach HEAD in all these cases.
 		 */
 		update_ref(msg, "HEAD", remote->old_sha1,
@@ -816,12 +815,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			our_head_points_at =
 				find_remote_branch(mapped_refs, option_branch);
 
-			if (!our_head_points_at) {
-				warning(_("Remote branch %s not found in "
-					"upstream %s, using HEAD instead"),
-					option_branch, option_origin);
-				our_head_points_at = remote_head_points_at;
-			}
+			if (!our_head_points_at)
+				die(_("Remote branch %s not found in upstream %s"),
+				    option_branch, option_origin);
 		}
 		else
 			our_head_points_at = remote_head_points_at;
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 7e85c71..5237066 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -282,13 +282,6 @@ test_expect_success 'clone shallow object count' '
 	test_cmp count3.expected count3.actual
 '
 
-test_expect_success 'clone shallow with nonexistent --branch' '
-	git clone --depth 1 --branch Z "file://$(pwd)/." shallow4 &&
-	GIT_DIR=shallow4/.git git rev-parse HEAD >actual &&
-	git rev-parse HEAD >expected &&
-	test_cmp expected actual
-'
-
 test_expect_success 'clone shallow with detached HEAD' '
 	git checkout HEAD^ &&
 	git clone --depth 1 "file://$(pwd)/." shallow5 &&
diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh
index f3f9a76..56be67e 100755
--- a/t/t5706-clone-branch.sh
+++ b/t/t5706-clone-branch.sh
@@ -57,12 +57,8 @@ test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
 	)
 '
 
-test_expect_success 'clone -b with bogus branch chooses HEAD' '
-	git clone -b bogus parent clone-bogus &&
-	(cd clone-bogus &&
-	 check_HEAD master &&
-	 check_file one
-	)
+test_expect_success 'clone -b with bogus branch' '
+	test_must_fail git clone -b bogus parent clone-bogus
 '
 
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 09/10] clone: allow --branch to take a tag

Because a tag ref cannot be put to HEAD, HEAD will become detached. This is consistent with "git checkout <tag>".

This is mostly useful in shallow clone, where it allows you to clone a tag in addtion to branches.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-clone.txt |    5 +++--
 builtin/clone.c             |   20 +++++++++++++++++++-
 t/t5500-fetch-pack.sh       |   15 +++++++++++++++
 t/t5601-clone.sh            |    9 +++++++++
 4 files changed, 46 insertions(+), 3 deletions(-)
Show changes to 4 files +46 −3

Documentation/git-clone.txt, builtin/clone.c, t/t5500-fetch-pack.sh, t/t5601-clone.sh

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 0931a3e..6e22522 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -147,8 +147,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
 	Instead of pointing the newly created HEAD to the branch pointed
 	to by the cloned repository's HEAD, point to `<name>` branch
-	instead. In a non-bare repository, this is the branch that will
-	be checked out.
+	instead. `--branch` can also take tags and treat them like
+	detached HEAD. In a non-bare repository, this is the branch
+	that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
diff --git a/builtin/clone.c b/builtin/clone.c
index 3cfedb3..651b4cc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -420,6 +420,15 @@ static struct ref *find_remote_branch(const struct ref *refs, const char *branch
 	strbuf_addstr(&head, branch);
 	ref = find_ref_by_name(refs, head.buf);
 	strbuf_release(&head);
+
+	if (ref)
+		return ref;
+
+	strbuf_addstr(&head, "refs/tags/");
+	strbuf_addstr(&head, branch);
+	ref = find_ref_by_name(refs, head.buf);
+	strbuf_release(&head);
+
 	return ref;
 }
 
@@ -441,8 +450,12 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 		if (!remote_head && option_branch)
 			warning(_("Could not find remote branch %s to clone."),
 				option_branch);
-		else
+		else {
 			get_fetch_map(remote_head, refspec, &tail, 0);
+
+			/* if --branch=tag, pull the requested tag explicitly */
+			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+		}
 	} else
 		get_fetch_map(refs, refspec, &tail, 0);
 
@@ -515,6 +528,11 @@ static void update_head(const struct ref *our, const struct ref *remote,
 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
 			install_branch_config(0, head, option_origin, our->name);
 		}
+	} else if (our) {
+		struct commit *c = lookup_commit_reference(our->old_sha1);
+		/* --branch specifies a non-branch (i.e. tags), detach HEAD */
+		update_ref(msg, "HEAD", c->object.sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
 	} else if (remote) {
 		/*
 		 * We know remote HEAD points to a non-branch, or
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 5237066..ce51692 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -311,4 +311,19 @@ EOF
 	test_cmp count6.expected count6.actual
 '
 
+test_expect_success 'shallow cloning single tag' '
+	git clone --depth 1 --branch=TAGB1 "file://$(pwd)/." shallow7 &&
+	cat >taglist.expected <<\EOF &&
+TAGB1
+TAGB2
+EOF
+	GIT_DIR=shallow7/.git git tag -l >taglist.actual &&
+	test_cmp taglist.expected taglist.actual &&
+
+	echo "in-pack: 7" > count7.expected &&
+	GIT_DIR=shallow7/.git git count-objects -v |
+		grep "^in-pack" > count7.actual &&
+	test_cmp count7.expected count7.actual
+'
+
 test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e0b8db6..67869b4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
 	grep /src/\\.git/objects target-10/objects/info/alternates
 '
 
+test_expect_success 'clone checking out a tag' '
+	git clone --branch=some-tag src dst.tag &&
+	GIT_DIR=src/.git git rev-parse some-tag >expected &&
+	test_cmp expected dst.tag/.git/HEAD &&
+	GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+	echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+	test_cmp fetch.expected fetch.actual
+'
+
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 16, 2012, 09:46 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 10/10] clone: print advice on checking out detached HEAD

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c           |   14 ++++++++++++++
 advice.h           |    1 +
 builtin/checkout.c |   16 +---------------
 builtin/clone.c    |    5 ++++-
 4 files changed, 20 insertions(+), 16 deletions(-)
Show changes to 4 files +20 −16

advice.c, advice.h, builtin/checkout.c, builtin/clone.c

diff --git a/advice.c b/advice.c
index e02e632..3e1a145 100644
--- a/advice.c
+++ b/advice.c
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)
 	error_resolve_conflict(me);
 	die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+	const char fmt[] =
+	"Note: checking out '%s'.\n\n"
+	"You are in 'detached HEAD' state. You can look around, make experimental\n"
+	"changes and commit them, and you can discard any commits you make in this\n"
+	"state without impacting any branches by performing another checkout.\n\n"
+	"If you want to create a new branch to retain commits you create, you may\n"
+	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
+	"  git checkout -b new_branch_name\n\n";
+
+	fprintf(stderr, fmt, new_name);
+}
diff --git a/advice.h b/advice.h
index e5d0af7..7bda45b 100644
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f1984d9..5bf96ba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-	const char fmt[] =
-	"Note: checking out '%s'.\n\n"
-	"You are in 'detached HEAD' state. You can look around, make experimental\n"
-	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
-	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b new_branch_name\n\n";
-
-	fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -575,7 +561,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old->path && advice_detached_head)
-				detach_advice(old->path, new->name);
+				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
 		}
 	} else if (new->path) {	/* Switch branches. */
diff --git a/builtin/clone.c b/builtin/clone.c
index 651b4cc..72eebca 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -563,7 +563,10 @@ static int checkout(void)
 			  "unable to checkout.\n"));
 		return 0;
 	}
-	if (strcmp(head, "HEAD")) {
+	if (!strcmp(head, "HEAD")) {
+		if (advice_detached_head)
+			detach_advice(sha1_to_hex(sha1));
+	} else {
 		if (prefixcmp(head, "refs/heads/"))
 			die(_("HEAD not found below refs/heads!"));
 	}
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 01/10] t5601: add missing && cascade

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t5601-clone.sh |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
Show changes to t/t5601-clone.sh +3 −3
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 87ee016..49821eb 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -9,9 +9,9 @@ test_expect_success setup '
 	rm -fr .git &&
 	test_create_repo src &&
 	(
-		cd src
-		>file
-		git add file
+		cd src &&
+		>file &&
+		git add file &&
 		git commit -m initial
 	)
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 02/10] clone: write detached HEAD in bare repositories

If we don't write, HEAD is still at refs/heads/master as initialized by init-db, which may or may not match remote's HEAD.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c  |    9 +++------
 t/t5601-clone.sh |   25 ++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 7 deletions(-)
Show changes to 2 files +27 −7

builtin/clone.c, t/t5601-clone.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 86db954..8dde1ea 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
-		if (!option_bare) {
-			update_ref(reflog_msg.buf, "HEAD",
-				   remote_head->old_sha1,
-				   NULL, REF_NODEREF, DIE_ON_ERR);
-			our_head_points_at = remote_head;
-		}
+		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+		our_head_points_at = remote_head;
 	} else {
 		/* Nothing to checkout out */
 		if (!option_no_checkout)
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 49821eb..e0b8db6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -12,7 +12,10 @@ test_expect_success setup '
 		cd src &&
 		>file &&
 		git add file &&
-		git commit -m initial
+		git commit -m initial &&
+		echo 1 >file &&
+		git add file &&
+		git commit -m updated
 	)
 
 '
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
 
 '
 
+test_expect_success 'clone --mirror with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --mirror src mirror.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --bare src bare.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
 test_expect_success 'clone --bare names the local repository <name>.git' '
 
 	git clone --bare src &&
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 03/10] clone: factor out checkout code

Read HEAD from disk instead of relying on local variable our_head_points_at, so that if earlier code fails to make HEAD properly, it'll be detected.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |  101 +++++++++++++++++++++++++++++++-----------------------
 1 files changed, 58 insertions(+), 43 deletions(-)
Show changes to builtin/clone.c +58 −43
diff --git a/builtin/clone.c b/builtin/clone.c
index 8dde1ea..100056d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static int checkout(void)
+{
+	unsigned char sha1[20];
+	char *head;
+	struct lock_file *lock_file;
+	struct unpack_trees_options opts;
+	struct tree *tree;
+	struct tree_desc t;
+	int err = 0, fd;
+
+	if (option_no_checkout)
+		return 0;
+
+	head = resolve_refdup("HEAD", sha1, 1, NULL);
+	if (!head) {
+		warning(_("remote HEAD refers to nonexistent ref, "
+			  "unable to checkout.\n"));
+		return 0;
+	}
+	if (strcmp(head, "HEAD")) {
+		if (prefixcmp(head, "refs/heads/"))
+			die(_("HEAD not found below refs/heads!"));
+	}
+	free(head);
+
+	/* We need to be in the new work tree for the checkout */
+	setup_work_tree();
+
+	lock_file = xcalloc(1, sizeof(struct lock_file));
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&opts, 0, sizeof opts);
+	opts.update = 1;
+	opts.merge = 1;
+	opts.fn = oneway_merge;
+	opts.verbose_update = (option_verbosity > 0);
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+
+	tree = parse_tree_indirect(sha1);
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	unpack_trees(1, &t, &opts);
+
+	if (write_cache(fd, active_cache, active_nr) ||
+	    commit_locked_index(lock_file))
+		die(_("unable to write new index file"));
+
+	err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+			sha1_to_hex(sha1), "1", NULL);
+
+	if (!err && option_recursive)
+		err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+	return err;
+}
+
 static int write_one_config(const char *key, const char *value, void *data)
 {
 	return git_config_set_multivar(key, value ? value : "true", "^$", 0);
@@ -722,13 +779,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
 			   NULL, REF_NODEREF, DIE_ON_ERR);
-		our_head_points_at = remote_head;
-	} else {
-		/* Nothing to checkout out */
-		if (!option_no_checkout)
-			warning(_("remote HEAD refers to nonexistent ref, "
-				"unable to checkout.\n"));
-		option_no_checkout = 1;
 	}
 
 	if (transport) {
@@ -736,42 +786,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		transport_disconnect(transport);
 	}
 
-	if (!option_no_checkout) {
-		struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-		struct unpack_trees_options opts;
-		struct tree *tree;
-		struct tree_desc t;
-		int fd;
-
-		/* We need to be in the new work tree for the checkout */
-		setup_work_tree();
-
-		fd = hold_locked_index(lock_file, 1);
-
-		memset(&opts, 0, sizeof opts);
-		opts.update = 1;
-		opts.merge = 1;
-		opts.fn = oneway_merge;
-		opts.verbose_update = (option_verbosity > 0);
-		opts.src_index = &the_index;
-		opts.dst_index = &the_index;
-
-		tree = parse_tree_indirect(our_head_points_at->old_sha1);
-		parse_tree(tree);
-		init_tree_desc(&t, tree->buffer, tree->size);
-		unpack_trees(1, &t, &opts);
-
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(lock_file))
-			die(_("unable to write new index file"));
-
-		err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-				sha1_to_hex(our_head_points_at->old_sha1), "1",
-				NULL);
-
-		if (!err && option_recursive)
-			err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-	}
+	err = checkout();
 
 	strbuf_release(&reflog_msg);
 	strbuf_release(&branch_top);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 04/10] clone: factor out HEAD update code

While at it, update the comment at "if (remote_head)"
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   41 ++++++++++++++++++++++++-----------------
 1 files changed, 24 insertions(+), 17 deletions(-)
Show changes to builtin/clone.c +24 −17
diff --git a/builtin/clone.c b/builtin/clone.c
index 100056d..baed5ae 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,29 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static void update_head(const struct ref *our, const struct ref *remote,
+			const char *msg)
+{
+	if (our) {
+		/* Local default branch link */
+		create_symref("HEAD", our->name, NULL);
+		if (!option_bare) {
+			const char *head = skip_prefix(our->name, "refs/heads/");
+			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+			install_branch_config(0, head, option_origin, our->name);
+		}
+	} else if (remote) {
+		/*
+		 * We know remote HEAD points to a non-branch, or
+		 * HEAD points to a branch but we don't know which one, or
+		 * we asked for a specific branch but it did not exist.
+		 * Detach HEAD in all these cases.
+		 */
+		update_ref(msg, "HEAD", remote->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+	}
+}
+
 static int checkout(void)
 {
 	unsigned char sha1[20];
@@ -763,23 +786,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			      reflog_msg.buf);
 	}
 
-	if (our_head_points_at) {
-		/* Local default branch link */
-		create_symref("HEAD", our_head_points_at->name, NULL);
-		if (!option_bare) {
-			const char *head = skip_prefix(our_head_points_at->name,
-						       "refs/heads/");
-			update_ref(reflog_msg.buf, "HEAD",
-				   our_head_points_at->old_sha1,
-				   NULL, 0, DIE_ON_ERR);
-			install_branch_config(0, head, option_origin,
-					      our_head_points_at->name);
-		}
-	} else if (remote_head) {
-		/* Source had detached HEAD pointing somewhere. */
-		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
-			   NULL, REF_NODEREF, DIE_ON_ERR);
-	}
+	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
 	if (transport) {
 		transport_unlock_pack(transport);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 05/10] clone: factor out remote ref writing

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   36 ++++++++++++++++++++++++------------
 1 files changed, 24 insertions(+), 12 deletions(-)
Show changes to builtin/clone.c +24 −12
diff --git a/builtin/clone.c b/builtin/clone.c
index baed5ae..d1d6dca 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,28 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static void update_remote_refs(const struct ref *refs,
+			       const struct ref *mapped_refs,
+			       const struct ref *remote_head_points_at,
+			       const char *branch_top,
+			       const char *msg)
+{
+	if (refs) {
+		clear_extra_refs();
+		write_remote_refs(mapped_refs);
+	}
+
+	if (remote_head_points_at && !option_bare) {
+		struct strbuf head_ref = STRBUF_INIT;
+		strbuf_addstr(&head_ref, branch_top);
+		strbuf_addstr(&head_ref, "HEAD");
+		create_symref(head_ref.buf,
+			      remote_head_points_at->peer_ref->name,
+			      msg);
+	}
+
+}
+
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
@@ -740,10 +762,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	if (refs) {
-		clear_extra_refs();
-
-		write_remote_refs(mapped_refs);
-
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
 			guess_remote_head(remote_head, mapped_refs, 0);
@@ -777,14 +795,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
-	if (remote_head_points_at && !option_bare) {
-		struct strbuf head_ref = STRBUF_INIT;
-		strbuf_addstr(&head_ref, branch_top.buf);
-		strbuf_addstr(&head_ref, "HEAD");
-		create_symref(head_ref.buf,
-			      remote_head_points_at->peer_ref->name,
-			      reflog_msg.buf);
-	}
+	update_remote_refs(refs, mapped_refs, remote_head_points_at,
+			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 06/10] clone: delay cloning until after remote HEAD checking

This gives us an opportunity to abort the command during remote HEAD check without wasting much bandwidth.

Cloning with remote-helper remains before the check because the remote helper updates mapped_refs, which is necessary for remote ref checks. foreign_vcs field is used to indicate the transport is handled by remote helper.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   54 +++++++++++++++++++++++++++---------------------------
 transport.c     |    5 ++++-
 2 files changed, 31 insertions(+), 28 deletions(-)
Show changes to 2 files +31 −28

builtin/clone.c, transport.c

diff --git a/builtin/clone.c b/builtin/clone.c
index d1d6dca..a9d06a4 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -361,13 +361,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	closedir(dir);
 }
 
-static const struct ref *clone_local(const char *src_repo,
-				     const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	const struct ref *ret;
-	struct remote *remote;
-	struct transport *transport;
-
 	if (option_shared) {
 		struct strbuf alt = STRBUF_INIT;
 		strbuf_addf(&alt, "%s/objects", src_repo);
@@ -383,13 +378,8 @@ static const struct ref *clone_local(const char *src_repo,
 		strbuf_release(&dest);
 	}
 
-	remote = remote_get(src_repo);
-	transport = transport_get(remote, src_repo);
-	ret = transport_get_remote_refs(transport);
-	transport_disconnect(transport);
 	if (0 <= option_verbosity)
 		printf(_("done.\n"));
-	return ret;
 }
 
 static const char *junk_work_tree;
@@ -581,6 +571,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
 	struct transport *transport = NULL;
 	char *src_ref_prefix = "refs/heads/";
+	struct remote *remote;
 	int err = 0;
 
 	struct refspec *refspec;
@@ -732,13 +723,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 	strbuf_reset(&value);
 
-	if (is_local) {
-		refs = clone_local(path, git_dir);
-		mapped_refs = wanted_peer_refs(refs, refspec);
-	} else {
-		struct remote *remote = remote_get(option_origin);
-		transport = transport_get(remote, remote->url[0]);
+	remote = remote_get(option_origin);
+	transport = transport_get(remote, remote->url[0]);
 
+	if (!is_local) {
 		if (!transport->get_refs_list || !transport->fetch)
 			die(_("Don't know how to clone %s"), transport->url);
 
@@ -753,14 +741,23 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		if (option_upload_pack)
 			transport_set_option(transport, TRANS_OPT_UPLOADPACK,
 					     option_upload_pack);
-
-		refs = transport_get_remote_refs(transport);
-		if (refs) {
-			mapped_refs = wanted_peer_refs(refs, refspec);
-			transport_fetch_refs(transport, mapped_refs);
-		}
 	}
 
+	refs = transport_get_remote_refs(transport);
+	mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
+
+	/*
+	 * mapped_refs may be updated if transport-helper is used so
+	 * we need fetch it early because remote_head code below
+	 * relies on it.
+	 *
+	 * for normal clones, transport_get_remote_refs() should
+	 * return reliable ref set, we can delay cloning until after
+	 * remote HEAD check.
+	 */
+	if (!is_local && remote->foreign_vcs && refs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	if (refs) {
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
@@ -795,15 +792,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	if (is_local)
+		clone_local(path, git_dir);
+	else if (refs && !remote->foreign_vcs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	update_remote_refs(refs, mapped_refs, remote_head_points_at,
 			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-	if (transport) {
-		transport_unlock_pack(transport);
-		transport_disconnect(transport);
-	}
+	transport_unlock_pack(transport);
+	transport_disconnect(transport);
 
 	err = checkout();
 
diff --git a/transport.c b/transport.c
index a99b7c9..4366639 100644
--- a/transport.c
+++ b/transport.c
@@ -895,8 +895,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
 
 		while (is_urlschemechar(p == url, *p))
 			p++;
-		if (!prefixcmp(p, "::"))
+		if (!prefixcmp(p, "::")) {
 			helper = xstrndup(url, p - url);
+			remote->foreign_vcs = helper;
+		}
 	}
 
 	if (helper) {
@@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
 		char *handler = xmalloc(len + 1);
 		handler[len] = 0;
 		strncpy(handler, url, len);
+		remote->foreign_vcs = handler;
 		transport_helper_init(ret, handler);
 	}
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 07/10] clone: --branch=<branch> always means refs/heads/<branch>

It does not make sense to look outside refs/heads for HEAD's target (src_ref_prefix can be set to "refs/" if --mirror is used) because ref code only allows symref in form refs/heads/...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/clone.c +2 −2
diff --git a/builtin/clone.c b/builtin/clone.c
index a9d06a4..89ba6f0 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -463,7 +463,7 @@ static void update_remote_refs(const struct ref *refs,
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
-	if (our) {
+	if (our && !prefixcmp(our->name, "refs/heads/")) {
 		/* Local default branch link */
 		create_symref("HEAD", our->name, NULL);
 		if (!option_bare) {
@@ -765,7 +765,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 		if (option_branch) {
 			struct strbuf head = STRBUF_INIT;
-			strbuf_addstr(&head, src_ref_prefix);
+			strbuf_addstr(&head, "refs/heads/");
 			strbuf_addstr(&head, option_branch);
 			our_head_points_at =
 				find_ref_by_name(mapped_refs, head.buf);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:22 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 08/10] clone: refuse to clone if --branch points to bogus ref

It's possible that users make a typo in the branch name. Stop and let users recheck. Falling back to remote's HEAD is not documented any way.

Except when using remote helper, the pack has not been transferred at this stage yet so we don't waste much bandwidth.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c         |   13 +++++--------
 t/t5706-clone-branch.sh |    8 ++------
 2 files changed, 7 insertions(+), 14 deletions(-)
Show changes to 2 files +7 −14

builtin/clone.c, t/t5706-clone-branch.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 89ba6f0..6a2886a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -474,8 +474,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
 	} else if (remote) {
 		/*
 		 * We know remote HEAD points to a non-branch, or
-		 * HEAD points to a branch but we don't know which one, or
-		 * we asked for a specific branch but it did not exist.
+		 * HEAD points to a branch but we don't know which one.
 		 * Detach HEAD in all these cases.
 		 */
 		update_ref(msg, "HEAD", remote->old_sha1,
@@ -771,12 +770,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 				find_ref_by_name(mapped_refs, head.buf);
 			strbuf_release(&head);
 
-			if (!our_head_points_at) {
-				warning(_("Remote branch %s not found in "
-					"upstream %s, using HEAD instead"),
-					option_branch, option_origin);
-				our_head_points_at = remote_head_points_at;
-			}
+			if (!our_head_points_at)
+				die(_("Remote branch %s not found in "
+				      "upstream %s, using HEAD instead"),
+				    option_branch, option_origin);
 		}
 		else
 			our_head_points_at = remote_head_points_at;
diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh
index f3f9a76..56be67e 100755
--- a/t/t5706-clone-branch.sh
+++ b/t/t5706-clone-branch.sh
@@ -57,12 +57,8 @@ test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
 	)
 '
 
-test_expect_success 'clone -b with bogus branch chooses HEAD' '
-	git clone -b bogus parent clone-bogus &&
-	(cd clone-bogus &&
-	 check_HEAD master &&
-	 check_file one
-	)
+test_expect_success 'clone -b with bogus branch' '
+	test_must_fail git clone -b bogus parent clone-bogus
 '
 
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:22 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 09/10] clone: allow --branch to take a tag

Because a tag ref cannot be put to HEAD, HEAD will become detached. This is consistent with "git checkout <tag>".

This is mostly useful in shallow clone, where it allows you to clone a tag in addtion to branches.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-clone.txt |    5 +++--
 builtin/clone.c             |   13 +++++++++++++
 t/t5601-clone.sh            |    9 +++++++++
 3 files changed, 25 insertions(+), 2 deletions(-)
Show changes to 3 files +25 −2

Documentation/git-clone.txt, builtin/clone.c, t/t5601-clone.sh

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 4b8b26b..db2b29c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -146,8 +146,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
 	Instead of pointing the newly created HEAD to the branch pointed
 	to by the cloned repository's HEAD, point to `<name>` branch
-	instead. In a non-bare repository, this is the branch that will
-	be checked out.
+	instead. `--branch` can also take tags and treat them like
+	detached HEAD. In a non-bare repository, this is the branch
+	that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
diff --git a/builtin/clone.c b/builtin/clone.c
index 6a2886a..a3c8f78 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -471,6 +471,11 @@ static void update_head(const struct ref *our, const struct ref *remote,
 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
 			install_branch_config(0, head, option_origin, our->name);
 		}
+	} else if (our) {
+		struct commit *c = lookup_commit_reference(our->old_sha1);
+		/* --branch specifies a non-branch (i.e. tags), detach HEAD */
+		update_ref(msg, "HEAD", c->object.sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
 	} else if (remote) {
 		/*
 		 * We know remote HEAD points to a non-branch, or
@@ -770,6 +775,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 				find_ref_by_name(mapped_refs, head.buf);
 			strbuf_release(&head);
 
+			if (!our_head_points_at) {
+				strbuf_addstr(&head, "refs/tags/");
+				strbuf_addstr(&head, option_branch);
+				our_head_points_at =
+					find_ref_by_name(mapped_refs, head.buf);
+				strbuf_release(&head);
+			}
+
 			if (!our_head_points_at)
 				die(_("Remote branch %s not found in "
 				      "upstream %s, using HEAD instead"),
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e0b8db6..67869b4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
 	grep /src/\\.git/objects target-10/objects/info/alternates
 '
 
+test_expect_success 'clone checking out a tag' '
+	git clone --branch=some-tag src dst.tag &&
+	GIT_DIR=src/.git git rev-parse some-tag >expected &&
+	test_cmp expected dst.tag/.git/HEAD &&
+	GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+	echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+	test_cmp fetch.expected fetch.actual
+'
+
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 13, 2012, 07:22 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 10/10] clone: print advice on checking out detached HEAD

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c           |   14 ++++++++++++++
 advice.h           |    1 +
 builtin/checkout.c |   16 +---------------
 builtin/clone.c    |    5 ++++-
 4 files changed, 20 insertions(+), 16 deletions(-)
Show changes to 4 files +20 −16

advice.c, advice.h, builtin/checkout.c, builtin/clone.c

diff --git a/advice.c b/advice.c
index e02e632..3e1a145 100644
--- a/advice.c
+++ b/advice.c
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)
 	error_resolve_conflict(me);
 	die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+	const char fmt[] =
+	"Note: checking out '%s'.\n\n"
+	"You are in 'detached HEAD' state. You can look around, make experimental\n"
+	"changes and commit them, and you can discard any commits you make in this\n"
+	"state without impacting any branches by performing another checkout.\n\n"
+	"If you want to create a new branch to retain commits you create, you may\n"
+	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
+	"  git checkout -b new_branch_name\n\n";
+
+	fprintf(stderr, fmt, new_name);
+}
diff --git a/advice.h b/advice.h
index e5d0af7..7bda45b 100644
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f1984d9..5bf96ba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-	const char fmt[] =
-	"Note: checking out '%s'.\n\n"
-	"You are in 'detached HEAD' state. You can look around, make experimental\n"
-	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
-	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b new_branch_name\n\n";
-
-	fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -575,7 +561,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old->path && advice_detached_head)
-				detach_advice(old->path, new->name);
+				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
 		}
 	} else if (new->path) {	/* Switch branches. */
diff --git a/builtin/clone.c b/builtin/clone.c
index a3c8f78..bfdafaa 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -506,7 +506,10 @@ static int checkout(void)
 			  "unable to checkout.\n"));
 		return 0;
 	}
-	if (strcmp(head, "HEAD")) {
+	if (!strcmp(head, "HEAD")) {
+		if (advice_detached_head)
+			detach_advice(sha1_to_hex(sha1));
+	} else {
 		if (prefixcmp(head, "refs/heads/"))
 			die(_("HEAD not found below refs/heads!"));
 	}
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 01/10] t5601: add missing && cascade

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t5601-clone.sh |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
Show changes to t/t5601-clone.sh +3 −3
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 87ee016..49821eb 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -9,9 +9,9 @@ test_expect_success setup '
 	rm -fr .git &&
 	test_create_repo src &&
 	(
-		cd src
-		>file
-		git add file
+		cd src &&
+		>file &&
+		git add file &&
 		git commit -m initial
 	)
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 02/10] clone: write detached HEAD in bare repositories

If we don't write, HEAD is still at refs/heads/master as initialized by init-db, which may or may not match remote's HEAD.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c  |    9 +++------
 t/t5601-clone.sh |   25 ++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 7 deletions(-)
Show changes to 2 files +27 −7

builtin/clone.c, t/t5601-clone.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 86db954..8dde1ea 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		}
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
-		if (!option_bare) {
-			update_ref(reflog_msg.buf, "HEAD",
-				   remote_head->old_sha1,
-				   NULL, REF_NODEREF, DIE_ON_ERR);
-			our_head_points_at = remote_head;
-		}
+		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+		our_head_points_at = remote_head;
 	} else {
 		/* Nothing to checkout out */
 		if (!option_no_checkout)
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 49821eb..e0b8db6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -12,7 +12,10 @@ test_expect_success setup '
 		cd src &&
 		>file &&
 		git add file &&
-		git commit -m initial
+		git commit -m initial &&
+		echo 1 >file &&
+		git add file &&
+		git commit -m updated
 	)
 
 '
@@ -88,6 +91,26 @@ test_expect_success 'clone --mirror' '
 
 '
 
+test_expect_success 'clone --mirror with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --mirror src mirror.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=mirror.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
+test_expect_success 'clone --bare with detached HEAD' '
+
+	( cd src && git checkout HEAD^ && git rev-parse HEAD >../expected ) &&
+	git clone --bare src bare.detached &&
+	( cd src && git checkout - ) &&
+	GIT_DIR=bare.detached git rev-parse HEAD >actual &&
+	test_cmp expected actual
+
+'
+
 test_expect_success 'clone --bare names the local repository <name>.git' '
 
 	git clone --bare src &&
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 03/10] clone: factor out checkout code

Read HEAD from disk instead of relying on local variable our_head_points_at. This reduces complexity of cmd_clone() a little bit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |  101 +++++++++++++++++++++++++++++++-----------------------
 1 files changed, 58 insertions(+), 43 deletions(-)
Show changes to builtin/clone.c +58 −43
diff --git a/builtin/clone.c b/builtin/clone.c
index 8dde1ea..100056d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static int checkout(void)
+{
+	unsigned char sha1[20];
+	char *head;
+	struct lock_file *lock_file;
+	struct unpack_trees_options opts;
+	struct tree *tree;
+	struct tree_desc t;
+	int err = 0, fd;
+
+	if (option_no_checkout)
+		return 0;
+
+	head = resolve_refdup("HEAD", sha1, 1, NULL);
+	if (!head) {
+		warning(_("remote HEAD refers to nonexistent ref, "
+			  "unable to checkout.\n"));
+		return 0;
+	}
+	if (strcmp(head, "HEAD")) {
+		if (prefixcmp(head, "refs/heads/"))
+			die(_("HEAD not found below refs/heads!"));
+	}
+	free(head);
+
+	/* We need to be in the new work tree for the checkout */
+	setup_work_tree();
+
+	lock_file = xcalloc(1, sizeof(struct lock_file));
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&opts, 0, sizeof opts);
+	opts.update = 1;
+	opts.merge = 1;
+	opts.fn = oneway_merge;
+	opts.verbose_update = (option_verbosity > 0);
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+
+	tree = parse_tree_indirect(sha1);
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	unpack_trees(1, &t, &opts);
+
+	if (write_cache(fd, active_cache, active_nr) ||
+	    commit_locked_index(lock_file))
+		die(_("unable to write new index file"));
+
+	err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+			sha1_to_hex(sha1), "1", NULL);
+
+	if (!err && option_recursive)
+		err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+	return err;
+}
+
 static int write_one_config(const char *key, const char *value, void *data)
 {
 	return git_config_set_multivar(key, value ? value : "true", "^$", 0);
@@ -722,13 +779,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
 			   NULL, REF_NODEREF, DIE_ON_ERR);
-		our_head_points_at = remote_head;
-	} else {
-		/* Nothing to checkout out */
-		if (!option_no_checkout)
-			warning(_("remote HEAD refers to nonexistent ref, "
-				"unable to checkout.\n"));
-		option_no_checkout = 1;
 	}
 
 	if (transport) {
@@ -736,42 +786,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		transport_disconnect(transport);
 	}
 
-	if (!option_no_checkout) {
-		struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-		struct unpack_trees_options opts;
-		struct tree *tree;
-		struct tree_desc t;
-		int fd;
-
-		/* We need to be in the new work tree for the checkout */
-		setup_work_tree();
-
-		fd = hold_locked_index(lock_file, 1);
-
-		memset(&opts, 0, sizeof opts);
-		opts.update = 1;
-		opts.merge = 1;
-		opts.fn = oneway_merge;
-		opts.verbose_update = (option_verbosity > 0);
-		opts.src_index = &the_index;
-		opts.dst_index = &the_index;
-
-		tree = parse_tree_indirect(our_head_points_at->old_sha1);
-		parse_tree(tree);
-		init_tree_desc(&t, tree->buffer, tree->size);
-		unpack_trees(1, &t, &opts);
-
-		if (write_cache(fd, active_cache, active_nr) ||
-		    commit_locked_index(lock_file))
-			die(_("unable to write new index file"));
-
-		err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-				sha1_to_hex(our_head_points_at->old_sha1), "1",
-				NULL);
-
-		if (!err && option_recursive)
-			err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-	}
+	err = checkout();
 
 	strbuf_release(&reflog_msg);
 	strbuf_release(&branch_top);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 04/10] clone: factor out HEAD update code

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   36 +++++++++++++++++++-----------------
 1 files changed, 19 insertions(+), 17 deletions(-)
Show changes to builtin/clone.c +19 −17
diff --git a/builtin/clone.c b/builtin/clone.c
index 100056d..26a037c 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,24 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static void update_head(const struct ref *our, const struct ref *remote,
+			const char *msg)
+{
+	if (our) {
+		/* Local default branch link */
+		create_symref("HEAD", our->name, NULL);
+		if (!option_bare) {
+			const char *head = skip_prefix(our->name, "refs/heads/");
+			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+			install_branch_config(0, head, option_origin, our->name);
+		}
+	} else if (remote) {
+		/* Source had detached HEAD pointing somewhere. */
+		update_ref(msg, "HEAD", remote->old_sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
+	}
+}
+
 static int checkout(void)
 {
 	unsigned char sha1[20];
@@ -763,23 +781,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			      reflog_msg.buf);
 	}
 
-	if (our_head_points_at) {
-		/* Local default branch link */
-		create_symref("HEAD", our_head_points_at->name, NULL);
-		if (!option_bare) {
-			const char *head = skip_prefix(our_head_points_at->name,
-						       "refs/heads/");
-			update_ref(reflog_msg.buf, "HEAD",
-				   our_head_points_at->old_sha1,
-				   NULL, 0, DIE_ON_ERR);
-			install_branch_config(0, head, option_origin,
-					      our_head_points_at->name);
-		}
-	} else if (remote_head) {
-		/* Source had detached HEAD pointing somewhere. */
-		update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1,
-			   NULL, REF_NODEREF, DIE_ON_ERR);
-	}
+	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
 	if (transport) {
 		transport_unlock_pack(transport);
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 05/10] clone: factor out remote ref writing

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |   36 ++++++++++++++++++++++++------------
 1 files changed, 24 insertions(+), 12 deletions(-)
Show changes to builtin/clone.c +24 −12
diff --git a/builtin/clone.c b/builtin/clone.c
index 26a037c..73d07ed 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -448,6 +448,28 @@ static void write_remote_refs(const struct ref *local_refs)
 	clear_extra_refs();
 }
 
+static void update_remote_refs(const struct ref *refs,
+			       const struct ref *mapped_refs,
+			       const struct ref *remote_head_points_at,
+			       const char *branch_top,
+			       const char *msg)
+{
+	if (refs) {
+		clear_extra_refs();
+		write_remote_refs(mapped_refs);
+	}
+
+	if (remote_head_points_at && !option_bare) {
+		struct strbuf head_ref = STRBUF_INIT;
+		strbuf_addstr(&head_ref, branch_top);
+		strbuf_addstr(&head_ref, "HEAD");
+		create_symref(head_ref.buf,
+			      remote_head_points_at->peer_ref->name,
+			      msg);
+	}
+
+}
+
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
@@ -735,10 +757,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	if (refs) {
-		clear_extra_refs();
-
-		write_remote_refs(mapped_refs);
-
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
 			guess_remote_head(remote_head, mapped_refs, 0);
@@ -772,14 +790,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
-	if (remote_head_points_at && !option_bare) {
-		struct strbuf head_ref = STRBUF_INIT;
-		strbuf_addstr(&head_ref, branch_top.buf);
-		strbuf_addstr(&head_ref, "HEAD");
-		create_symref(head_ref.buf,
-			      remote_head_points_at->peer_ref->name,
-			      reflog_msg.buf);
-	}
+	update_remote_refs(refs, mapped_refs, remote_head_points_at,
+			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

This gives us an opportunity to abort the command during remote HEAD check without wasting much bandwidth.

Cloning with remote-helper remains before the check because the remote helper updates mapped_refs, which is necessary for remote ref checks.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 I'm not familiar with remote-helper to see if there's any better way
 to do this..
 builtin/clone.c |   54 +++++++++++++++++++++++++++---------------------------
 transport.c     |    5 ++++-
 2 files changed, 31 insertions(+), 28 deletions(-)
Show changes to 2 files +31 −28

builtin/clone.c, transport.c

diff --git a/builtin/clone.c b/builtin/clone.c
index 73d07ed..05224d7 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -361,13 +361,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	closedir(dir);
 }
 
-static const struct ref *clone_local(const char *src_repo,
-				     const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	const struct ref *ret;
-	struct remote *remote;
-	struct transport *transport;
-
 	if (option_shared) {
 		struct strbuf alt = STRBUF_INIT;
 		strbuf_addf(&alt, "%s/objects", src_repo);
@@ -383,13 +378,8 @@ static const struct ref *clone_local(const char *src_repo,
 		strbuf_release(&dest);
 	}
 
-	remote = remote_get(src_repo);
-	transport = transport_get(remote, src_repo);
-	ret = transport_get_remote_refs(transport);
-	transport_disconnect(transport);
 	if (0 <= option_verbosity)
 		printf(_("done.\n"));
-	return ret;
 }
 
 static const char *junk_work_tree;
@@ -576,6 +566,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
 	struct transport *transport = NULL;
 	char *src_ref_prefix = "refs/heads/";
+	struct remote *remote;
 	int err = 0;
 
 	struct refspec *refspec;
@@ -727,13 +718,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 	strbuf_reset(&value);
 
-	if (is_local) {
-		refs = clone_local(path, git_dir);
-		mapped_refs = wanted_peer_refs(refs, refspec);
-	} else {
-		struct remote *remote = remote_get(option_origin);
-		transport = transport_get(remote, remote->url[0]);
+	remote = remote_get(option_origin);
+	transport = transport_get(remote, remote->url[0]);
 
+	if (!is_local) {
 		if (!transport->get_refs_list || !transport->fetch)
 			die(_("Don't know how to clone %s"), transport->url);
 
@@ -748,14 +736,23 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		if (option_upload_pack)
 			transport_set_option(transport, TRANS_OPT_UPLOADPACK,
 					     option_upload_pack);
-
-		refs = transport_get_remote_refs(transport);
-		if (refs) {
-			mapped_refs = wanted_peer_refs(refs, refspec);
-			transport_fetch_refs(transport, mapped_refs);
-		}
 	}
 
+	refs = transport_get_remote_refs(transport);
+	mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
+
+	/*
+	 * mapped_refs may be updated if transport-helper is used so
+	 * we need fetch it early because remote_head code below
+	 * relies on it.
+	 *
+	 * for normal clones, transport_get_remote_refs() should
+	 * return reliable ref set, we can delay cloning until after
+	 * remote HEAD check.
+	 */
+	if (!is_local && remote->foreign_vcs && refs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	if (refs) {
 		remote_head = find_ref_by_name(refs, "HEAD");
 		remote_head_points_at =
@@ -790,15 +787,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	if (is_local)
+		clone_local(path, git_dir);
+	else if (refs && !remote->foreign_vcs)
+		transport_fetch_refs(transport, mapped_refs);
+
 	update_remote_refs(refs, mapped_refs, remote_head_points_at,
 			   branch_top.buf, reflog_msg.buf);
 
 	update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-	if (transport) {
-		transport_unlock_pack(transport);
-		transport_disconnect(transport);
-	}
+	transport_unlock_pack(transport);
+	transport_disconnect(transport);
 
 	err = checkout();
 
diff --git a/transport.c b/transport.c
index a99b7c9..aae9889 100644
--- a/transport.c
+++ b/transport.c
@@ -895,8 +895,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
 
 		while (is_urlschemechar(p == url, *p))
 			p++;
-		if (!prefixcmp(p, "::"))
+		if (!prefixcmp(p, "::")) {
 			helper = xstrndup(url, p - url);
+			remote->foreign_vcs = helper;
+		}
 	}
 
 	if (helper) {
@@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
 		char *handler = xmalloc(len + 1);
 		handler[len] = 0;
 		strncpy(handler, url, len);
+		remote->foreign_vcs = helper;
 		transport_helper_init(ret, handler);
 	}
 
-- 
1.7.3.1.256.g2539c.dirty
Junio C Hamano· Jan 11, 2012, 19:36 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
Show 26 quoted lines
> This gives us an opportunity to abort the command during remote HEAD
> check without wasting much bandwidth.
>
> Cloning with remote-helper remains before the check because the remote
> helper updates mapped_refs, which is necessary for remote ref checks.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  I'm not familiar with remote-helper to see if there's any better way
>  to do this..
> ...
> +	refs = transport_get_remote_refs(transport);
> +	mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
> +
> +	/*
> +	 * mapped_refs may be updated if transport-helper is used so
> +	 * we need fetch it early because remote_head code below
> +	 * relies on it.
> +	 *
> +	 * for normal clones, transport_get_remote_refs() should
> +	 * return reliable ref set, we can delay cloning until after
> +	 * remote HEAD check.
> +	 */
> +	if (!is_local && remote->foreign_vcs && refs)
> +		transport_fetch_refs(transport, mapped_refs);
> +

I like the goal of this change, but wouldn't remote->url indicate it is a remote that needs a helper invocation by having double-colon in it, without having an explicit value in foreign_vcs field?

Would it be cleaner if transport_get() learned to mark the transport as needing special treatment (i.e. we won't know the final ref mapping until we fetch the data from the other side) and check is made on that mark left in the transport, instead of foreign_vcs in remote?

Show 16 quoted lines
> diff --git a/transport.c b/transport.c
> index a99b7c9..aae9889 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -895,8 +895,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
>  
>  		while (is_urlschemechar(p == url, *p))
>  			p++;
> -		if (!prefixcmp(p, "::"))
> +		if (!prefixcmp(p, "::")) {
>  			helper = xstrndup(url, p - url);
> +			remote->foreign_vcs = helper;
> +		}
>  	}
>  
>  	if (helper) {

Ahhh, Ok. You are reusing the existing "foreign_vcs" field exactly for that purpose. Earlier the field was strictly for configured .vcs value, but now you use it also for the helper embedded within the URL, which sounds like the right change to me.

Show 7 quoted lines
> @@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
>  		char *handler = xmalloc(len + 1);
>  		handler[len] = 0;
>  		strncpy(handler, url, len);
> +		remote->foreign_vcs = helper;
>  		transport_helper_init(ret, handler);
>  	}

This I am not sure. What value does "helper" variable have at this point in the flow? Wouldn't it be a NULL? Or did you mean "handler"?

Nguyen Thai Ngoc Duy· Jan 12, 2012, 01:27 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

2012/1/12 Junio C Hamano <gitster@pobox.com>:
Show 10 quoted lines
>> @@ -938,6 +940,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
>>               char *handler = xmalloc(len + 1);
>>               handler[len] = 0;
>>               strncpy(handler, url, len);
>> +             remote->foreign_vcs = helper;
>>               transport_helper_init(ret, handler);
>>       }
>
> This I am not sure. What value does "helper" variable have at this point
> in the flow? Wouldn't it be a NULL? Or did you mean "handler"?
Ah yes "handler", my bad.
-- 
Duy
Junio C Hamano· Jan 24, 2012, 00:15 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

I wanted to merge this series to 'next' and further merge it early in the next cycle to 'master', but with this my pushes (!!) seem to fail.

It breaks pushing to multiple URLs like this:
    $ cat .git/config
    [remote "origin"]
            url = https://code.google.com/p/git-htmldocs/
            url = github.com:gitster/git-htmldocs.git
            push = refs/heads/master:refs/heads/master
    $ git push

The second url that is supposed to use the git-over-ssh transport mistakenly use https:// and fails with:

    error: Couldn't resolve host 'github.com:gitster' while accessing
    github.com:gitster/git-htmldocs.git/info/refs
    fatal: HTTP request failed
Junio C Hamano· Jan 24, 2012, 00:34 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

Junio C Hamano <gitster@pobox.com> writes:
Show 15 quoted lines
> It breaks pushing to multiple URLs like this:
>
>     $ cat .git/config
>     [remote "origin"]
>             url = https://code.google.com/p/git-htmldocs/
>             url = github.com:gitster/git-htmldocs.git
>             push = refs/heads/master:refs/heads/master
>     $ git push
>
> The second url that is supposed to use the git-over-ssh transport
> mistakenly use https:// and fails with:
>
>     error: Couldn't resolve host 'github.com:gitster' while accessing
>     github.com:gitster/git-htmldocs.git/info/refs
>     fatal: HTTP request failed
And here is an obvious band-aid to work it around.
Junio C Hamano· Jan 24, 2012, 00:39 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

Junio C Hamano <gitster@pobox.com> writes:
Show 19 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> It breaks pushing to multiple URLs like this:
>>
>>     $ cat .git/config
>>     [remote "origin"]
>>             url = https://code.google.com/p/git-htmldocs/
>>             url = github.com:gitster/git-htmldocs.git
>>             push = refs/heads/master:refs/heads/master
>>     $ git push
>>
>> The second url that is supposed to use the git-over-ssh transport
>> mistakenly use https:// and fails with:
>>
>>     error: Couldn't resolve host 'github.com:gitster' while accessing
>>     github.com:gitster/git-htmldocs.git/info/refs
>>     fatal: HTTP request failed
>
> And here is an obvious band-aid to work it around.
-- >8 --
Subject: [PATCH] push: do not let configured foreign-vcs permanently clobbered

Recently, 6f48d39 (clone: delay cloning until after remote HEAD checking, 2012-01-16) tried to record if a remote helper needs to be called after parsing the remote when transport_get() is called, by overwriting the field meant to store the configured remote helper name in the remote structure.

This is OK when a remote represents a single remote repository, but fails miserably when pushing to locations with multiple URLs, like this:

    $ cat .git/config
    [remote "origin"]
        url = https://code.google.com/p/git-htmldocs/
        url = github.com:gitster/git-htmldocs.git
        push = refs/heads/master:refs/heads/master
    $ git push

The second url that is supposed to use the git-over-ssh transport mistakenly use https:// and fails with:

    error: Couldn't resolve host 'github.com:gitster' while accessing
    github.com:gitster/git-htmldocs.git/info/refs
    fatal: HTTP request failed

The right solution would probably be to dedicate a separate field to store the detected external helper to be used, which is valid only during a single use of transport until it is disconnected, instead of overwriting foreign_vcs field, but in the meantime, this band-aid should suffice.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/push.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
Show changes to builtin/push.c +2 −0
diff --git a/builtin/push.c b/builtin/push.c
index 35cce53..5fb98a0 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -204,11 +204,13 @@ static int do_push(const char *repo, int flags)
 		url_nr = remote->url_nr;
 	}
 	if (url_nr) {
+		const char *configured_foreign_vcs = remote->foreign_vcs;
 		for (i = 0; i < url_nr; i++) {
 			struct transport *transport =
 				transport_get(remote, url[i]);
 			if (push_with_options(transport, flags))
 				errs++;
+			remote->foreign_vcs = configured_foreign_vcs;
 		}
 	} else {
 		struct transport *transport =
-- 
1.7.9.rc2.100.gfd863d
Nguyen Thai Ngoc Duy· Jan 24, 2012, 07:01 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 06/10] clone: delay cloning until after remote HEAD checking

2012/1/24 Junio C Hamano <gitster@pobox.com>:
Show 14 quoted lines
> Recently, 6f48d39 (clone: delay cloning until after remote HEAD checking,
> 2012-01-16) tried to record if a remote helper needs to be called after
> parsing the remote when transport_get() is called, by overwriting the
> field meant to store the configured remote helper name in the remote
> structure.
>
> This is OK when a remote represents a single remote repository, but fails
> miserably when pushing to locations with multiple URLs, like this:
>
>    $ cat .git/config
>    [remote "origin"]
>        url = https://code.google.com/p/git-htmldocs/
>        url = github.com:gitster/git-htmldocs.git
>        push = refs/heads/master:refs/heads/master

My bad. remote.*.vcs (or remote->foreign_vcs) is designed to override remote helper detection based on url. Once you have pushed over https, the following urls will be over https too. Luckily no other places perform an operation over multiple urls like push so we're safe with your bandage.

We should have another way to detect whether remote helper being used (comparing some function pointers like transport->connect or transport->disconnect may help). But I need to think a bit about delaying cloning for http(s) too even though remote helper is used.

-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 07/10] clone: --branch=<branch> always means refs/heads/<branch>

It does not make sense to look outside refs/heads for HEAD's target (src_ref_prefix can be set to "refs/" if --mirror is used) because ref code only allows symref in form refs/heads/...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/clone.c +2 −2
diff --git a/builtin/clone.c b/builtin/clone.c
index 05224d7..960242f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -463,7 +463,7 @@ static void update_remote_refs(const struct ref *refs,
 static void update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
-	if (our) {
+	if (our && !prefixcmp(our->name, "refs/heads/")) {
 		/* Local default branch link */
 		create_symref("HEAD", our->name, NULL);
 		if (!option_bare) {
@@ -760,7 +760,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
 		if (option_branch) {
 			struct strbuf head = STRBUF_INIT;
-			strbuf_addstr(&head, src_ref_prefix);
+			strbuf_addstr(&head, "refs/heads/");
 			strbuf_addstr(&head, option_branch);
 			our_head_points_at =
 				find_ref_by_name(mapped_refs, head.buf);
-- 
1.7.3.1.256.g2539c.dirty
Junio C Hamano· Jan 11, 2012, 20:01 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 07/10] clone: --branch=<branch> always means refs/heads/<branch>

Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
Show 22 quoted lines
> It does not make sense to look outside refs/heads for HEAD's target
> (src_ref_prefix can be set to "refs/" if --mirror is used) because ref
> code only allows symref in form refs/heads/...
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/clone.c |    4 ++--
>  1 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 05224d7..960242f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -463,7 +463,7 @@ static void update_remote_refs(const struct ref *refs,
>  static void update_head(const struct ref *our, const struct ref *remote,
>  			const char *msg)
>  {
> -	if (our) {
> +	if (our && !prefixcmp(our->name, "refs/heads/")) {
>  		/* Local default branch link */
>  		create_symref("HEAD", our->name, NULL);
>  		if (!option_bare) {
I think this makes sense. In the following three casees:
 - When cloning without --branch, if we could not find a branch that match
   HEAD at the remote;
 - When cloning with --branch, if we did not see such a branch at the
   remote; or
 - When cloning from an empty repository

we come here with "our" set to NULL. Additionally, if the remote HEAD points outside refs/heads/ and the transport could detect that case (e.g. a helper that reads from ls-remote output), we can see our->name outside refs/heads/. Is there any other case where our is not NULL and our->name does not start with refs/heads/?

The "else if (remote)" clause (not shown, outside the context) that follows still has comments that says it is a case for "Source had detached HEAD pointing somewhere", and needs to be adjusted for this change, no? It is "(1) we know the HEAD points at a non-branch, (2) HEAD may point at a branch but we do not know which one, or (3) we asked for a specific branch but it did not exist there" (cloning from an empty will have NULL in remote and the comment would not apply to that case).

Show 9 quoted lines
> @@ -760,7 +760,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>  
>  		if (option_branch) {
>  			struct strbuf head = STRBUF_INIT;
> -			strbuf_addstr(&head, src_ref_prefix);
> +			strbuf_addstr(&head, "refs/heads/");
>  			strbuf_addstr(&head, option_branch);
>  			our_head_points_at =
>  				find_ref_by_name(mapped_refs, head.buf);
Nguyen Thai Ngoc Duy· Jan 12, 2012, 03:00 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 07/10] clone: --branch=<branch> always means refs/heads/<branch>

2012/1/12 Junio C Hamano <gitster@pobox.com>:
Show 40 quoted lines
> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>
>> It does not make sense to look outside refs/heads for HEAD's target
>> (src_ref_prefix can be set to "refs/" if --mirror is used) because ref
>> code only allows symref in form refs/heads/...
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>>  builtin/clone.c |    4 ++--
>>  1 files changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/builtin/clone.c b/builtin/clone.c
>> index 05224d7..960242f 100644
>> --- a/builtin/clone.c
>> +++ b/builtin/clone.c
>> @@ -463,7 +463,7 @@ static void update_remote_refs(const struct ref *refs,
>>  static void update_head(const struct ref *our, const struct ref *remote,
>>                       const char *msg)
>>  {
>> -     if (our) {
>> +     if (our && !prefixcmp(our->name, "refs/heads/")) {
>>               /* Local default branch link */
>>               create_symref("HEAD", our->name, NULL);
>>               if (!option_bare) {
>
> I think this makes sense. In the following three casees:
>
>  - When cloning without --branch, if we could not find a branch that match
>   HEAD at the remote;
>
>  - When cloning with --branch, if we did not see such a branch at the
>   remote; or
>
>  - When cloning from an empty repository
>
> we come here with "our" set to NULL. Additionally, if the remote HEAD
> points outside refs/heads/ and the transport could detect that case
> (e.g. a helper that reads from ls-remote output), we can see our->name
> outside refs/heads/. Is there any other case where our is not NULL and
> our->name does not start with refs/heads/?

No, this patch pretty much guarantees that our->name must be inside refs/heads, unless remote's HEAD points outside, which makes it a broken remote and git-upload-pack should refuse to serve in the first place. Until --branch=tag comes into the picture.

Show 7 quoted lines
> The "else if (remote)" clause (not shown, outside the context) that
> follows still has comments that says it is a case for "Source had detached
> HEAD pointing somewhere", and needs to be adjusted for this change, no? It
> is "(1) we know the HEAD points at a non-branch, (2) HEAD may point at a
> branch but we do not know which one, or (3) we asked for a specific branch
> but it did not exist there" (cloning from an empty will have NULL in
> remote and the comment would not apply to that case).
Yes.
-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 08/10] clone: refuse to clone if --branch points to bogus ref

It's possible that users make a typo in the branch name. Stop and let users recheck. Falling back to remote's HEAD is not documented any way.

Except when using remote helper, the pack has not been transferred at this stage yet so we don't waste much bandwidth.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c         |   10 ++++------
 t/t5706-clone-branch.sh |    8 ++------
 2 files changed, 6 insertions(+), 12 deletions(-)
Show changes to 2 files +6 −12

builtin/clone.c, t/t5706-clone-branch.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 960242f..f751997 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -766,12 +766,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 				find_ref_by_name(mapped_refs, head.buf);
 			strbuf_release(&head);
 
-			if (!our_head_points_at) {
-				warning(_("Remote branch %s not found in "
-					"upstream %s, using HEAD instead"),
-					option_branch, option_origin);
-				our_head_points_at = remote_head_points_at;
-			}
+			if (!our_head_points_at)
+				die(_("Remote branch %s not found in "
+				      "upstream %s, using HEAD instead"),
+				    option_branch, option_origin);
 		}
 		else
 			our_head_points_at = remote_head_points_at;
diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh
index f3f9a76..56be67e 100755
--- a/t/t5706-clone-branch.sh
+++ b/t/t5706-clone-branch.sh
@@ -57,12 +57,8 @@ test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
 	)
 '
 
-test_expect_success 'clone -b with bogus branch chooses HEAD' '
-	git clone -b bogus parent clone-bogus &&
-	(cd clone-bogus &&
-	 check_HEAD master &&
-	 check_file one
-	)
+test_expect_success 'clone -b with bogus branch' '
+	test_must_fail git clone -b bogus parent clone-bogus
 '
 
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 09/10] clone: allow --branch to take a tag

Because a tag ref cannot be put to HEAD, HEAD will become detached. This is consistent with "git checkout <tag>".

This is mostly useful in shallow clone, where it allows you to clone a tag in addtion to branches.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-clone.txt |    5 +++--
 builtin/clone.c             |   13 +++++++++++++
 t/t5601-clone.sh            |    9 +++++++++
 3 files changed, 25 insertions(+), 2 deletions(-)
Show changes to 3 files +25 −2

Documentation/git-clone.txt, builtin/clone.c, t/t5601-clone.sh

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 4b8b26b..db2b29c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -146,8 +146,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
 	Instead of pointing the newly created HEAD to the branch pointed
 	to by the cloned repository's HEAD, point to `<name>` branch
-	instead. In a non-bare repository, this is the branch that will
-	be checked out.
+	instead. `--branch` can also take tags and treat them like
+	detached HEAD. In a non-bare repository, this is the branch
+	that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
diff --git a/builtin/clone.c b/builtin/clone.c
index f751997..ed18c1a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -471,6 +471,11 @@ static void update_head(const struct ref *our, const struct ref *remote,
 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
 			install_branch_config(0, head, option_origin, our->name);
 		}
+	} else if (our) {
+		struct commit *c = lookup_commit_reference(our->old_sha1);
+		/* Source had detached HEAD pointing somewhere. */
+		update_ref(msg, "HEAD", c->object.sha1,
+			   NULL, REF_NODEREF, DIE_ON_ERR);
 	} else if (remote) {
 		/* Source had detached HEAD pointing somewhere. */
 		update_ref(msg, "HEAD", remote->old_sha1,
@@ -766,6 +771,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 				find_ref_by_name(mapped_refs, head.buf);
 			strbuf_release(&head);
 
+			if (!our_head_points_at) {
+				strbuf_addstr(&head, "refs/tags/");
+				strbuf_addstr(&head, option_branch);
+				our_head_points_at =
+					find_ref_by_name(mapped_refs, head.buf);
+				strbuf_release(&head);
+			}
+
 			if (!our_head_points_at)
 				die(_("Remote branch %s not found in "
 				      "upstream %s, using HEAD instead"),
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index e0b8db6..67869b4 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' '
 	grep /src/\\.git/objects target-10/objects/info/alternates
 '
 
+test_expect_success 'clone checking out a tag' '
+	git clone --branch=some-tag src dst.tag &&
+	GIT_DIR=src/.git git rev-parse some-tag >expected &&
+	test_cmp expected dst.tag/.git/HEAD &&
+	GIT_DIR=dst.tag/.git git config remote.origin.fetch >fetch.actual &&
+	echo "+refs/heads/*:refs/remotes/origin/*" >fetch.expected &&
+	test_cmp fetch.expected fetch.actual
+'
+
 test_done
-- 
1.7.3.1.256.g2539c.dirty
Junio C Hamano· Jan 11, 2012, 23:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 09/10] clone: allow --branch to take a tag

Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
Show 12 quoted lines
> @@ -766,6 +771,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
>  				find_ref_by_name(mapped_refs, head.buf);
>  			strbuf_release(&head);
>  
> +			if (!our_head_points_at) {
> +				strbuf_addstr(&head, "refs/tags/");
> +				strbuf_addstr(&head, option_branch);
> +				our_head_points_at =
> +					find_ref_by_name(mapped_refs, head.buf);
> +				strbuf_release(&head);
> +			}
> +
Ok. I think this makes sense.
Nguyễn Thái Ngọc Duy· Jan 10, 2012, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 10/10] clone: print advice on checking out detached HEAD

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c           |   14 ++++++++++++++
 advice.h           |    1 +
 builtin/checkout.c |   16 +---------------
 builtin/clone.c    |    5 ++++-
 4 files changed, 20 insertions(+), 16 deletions(-)
Show changes to 4 files +20 −16

advice.c, advice.h, builtin/checkout.c, builtin/clone.c

diff --git a/advice.c b/advice.c
index e02e632..3e1a145 100644
--- a/advice.c
+++ b/advice.c
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)
 	error_resolve_conflict(me);
 	die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+	const char fmt[] =
+	"Note: checking out '%s'.\n\n"
+	"You are in 'detached HEAD' state. You can look around, make experimental\n"
+	"changes and commit them, and you can discard any commits you make in this\n"
+	"state without impacting any branches by performing another checkout.\n\n"
+	"If you want to create a new branch to retain commits you create, you may\n"
+	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
+	"  git checkout -b new_branch_name\n\n";
+
+	fprintf(stderr, fmt, new_name);
+}
diff --git a/advice.h b/advice.h
index e5d0af7..7bda45b 100644
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f1984d9..5bf96ba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-	const char fmt[] =
-	"Note: checking out '%s'.\n\n"
-	"You are in 'detached HEAD' state. You can look around, make experimental\n"
-	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
-	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b new_branch_name\n\n";
-
-	fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -575,7 +561,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old->path && advice_detached_head)
-				detach_advice(old->path, new->name);
+				detach_advice(new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
 		}
 	} else if (new->path) {	/* Switch branches. */
diff --git a/builtin/clone.c b/builtin/clone.c
index ed18c1a..70f0280 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -502,7 +502,10 @@ static int checkout(void)
 			  "unable to checkout.\n"));
 		return 0;
 	}
-	if (strcmp(head, "HEAD")) {
+	if (!strcmp(head, "HEAD")) {
+		if (advice_detached_head)
+			detach_advice(sha1_to_hex(sha1));
+	} else {
 		if (prefixcmp(head, "refs/heads/"))
 			die(_("HEAD not found below refs/heads!"));
 	}
-- 
1.7.3.1.256.g2539c.dirty

← back to recent threads