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 <redacted>
---
builtin/clone.c | 20 +++++++++++++++++++-
1 files changed, 19 insertions(+), 1 deletions(-)
@@ -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/")){+conststructref*ref=our_head_points_at;+structobject*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);+}elseif(our_head_points_at){/* Local default branch link */create_symref("HEAD",our_head_points_at->name,NULL);if(!option_bare){
From: Jeff King <hidden> Date: 2016-06-15 22:52:43
On Thu, Jan 05, 2012 at 08:49:40PM +0700, Nguyen Thai Ngoc Duy wrote:
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.
quoted hunk
@@ -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.
quoted hunk
@@ -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.
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
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
From: Jeff King <hidden> Date: 2016-06-15 22:52:44
On Fri, Jan 06, 2012 at 06:09:16PM +0700, Nguyen Thai Ngoc Duy wrote:
quoted
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
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 <redacted>
---
builtin/clone.c | 9 +++------
t/t5601-clone.sh | 25 ++++++++++++++++++++++++-
2 files changed, 27 insertions(+), 7 deletions(-)
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)}}elseif(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)
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 <redacted>
---
builtin/clone.c | 101 +++++++++++++++++++++++++++++++-----------------------
1 files changed, 58 insertions(+), 43 deletions(-)
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)clear_extra_refs();}+staticintcheckout(void)+{+unsignedcharsha1[20];+char*head;+structlock_file*lock_file;+structunpack_trees_optionsopts;+structtree*tree;+structtree_desct;+interr=0,fd;++if(option_no_checkout)+return0;++head=resolve_refdup("HEAD",sha1,1,NULL);+if(!head){+warning(_("remote HEAD refers to nonexistent ref, "+"unable to checkout.\n"));+return0;+}+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(structlock_file));+fd=hold_locked_index(lock_file,1);++memset(&opts,0,sizeofopts);+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);++returnerr;+}+staticintwrite_one_config(constchar*key,constchar*value,void*data){returngit_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){-structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));-structunpack_trees_optionsopts;-structtree*tree;-structtree_desct;-intfd;--/* 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,sizeofopts);-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);
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 <redacted>
---
builtin/clone.c | 5 +++--
1 files changed, 3 insertions(+), 2 deletions(-)
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)error_resolve_conflict(me);die("Exiting because of an unresolved conflict.");}++voiddetach_advice(constchar*new_name)+{+constcharfmt[]=+"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);+}
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)strbuf_release(&sb);}-staticvoiddetach_advice(constchar*old_path,constchar*new_name)-{-constcharfmt[]=-"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);-}-staticvoidupdate_refs_for_switch(structcheckout_opts*opts,structbranch_info*old,structbranch_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);}}elseif(new->path){/* Switch branches. */
@@ -467,7 +467,10 @@ static int checkout(void)"unable to checkout.\n"));return0;}-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!"));}
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 <redacted>
---
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(-)
@@ -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>::
@@ -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);}+}elseif(our_head_points_at){+conststructref*ref=our_head_points_at;+structcommit*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);}elseif(remote_head){/* Source had detached HEAD pointing somewhere. */update_ref(reflog_msg.buf,"HEAD",remote_head->old_sha1,
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' 'grep/src/\\.git/objectstarget-10/objects/info/alternates'+test_expect_success'clone checking out a tag''+gitclone--branch=some-tagsrcdst.tag&&+GIT_DIR=src/.gitgitrev-parsesome-tag>expected&&+test_cmpexpecteddst.tag/.git/HEAD&&+GIT_DIR=dst.tag/.gitgitconfigremote.origin.fetch>fetch.actual&&+echo"+refs/heads/*:refs/remotes/origin/*">fetch.expected&&+test_cmpfetch.expectedfetch.actual+'+ test_done
From: Junio C Hamano <hidden> Date: 2016-06-15 22:52:45
Nguyễn Thái Ngọc Duy [off-list ref] 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.
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
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
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 <redacted>
---
builtin/clone.c | 9 +++------
t/t5601-clone.sh | 25 ++++++++++++++++++++++++-
2 files changed, 27 insertions(+), 7 deletions(-)
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)}}elseif(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)
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 <redacted>
---
builtin/clone.c | 101 +++++++++++++++++++++++++++++++-----------------------
1 files changed, 58 insertions(+), 43 deletions(-)
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)clear_extra_refs();}+staticintcheckout(void)+{+unsignedcharsha1[20];+char*head;+structlock_file*lock_file;+structunpack_trees_optionsopts;+structtree*tree;+structtree_desct;+interr=0,fd;++if(option_no_checkout)+return0;++head=resolve_refdup("HEAD",sha1,1,NULL);+if(!head){+warning(_("remote HEAD refers to nonexistent ref, "+"unable to checkout.\n"));+return0;+}+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(structlock_file));+fd=hold_locked_index(lock_file,1);++memset(&opts,0,sizeofopts);+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);++returnerr;+}+staticintwrite_one_config(constchar*key,constchar*value,void*data){returngit_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){-structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));-structunpack_trees_optionsopts;-structtree*tree;-structtree_desct;-intfd;--/* 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,sizeofopts);-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);
@@ -448,6 +448,24 @@ static void write_remote_refs(const struct ref *local_refs)clear_extra_refs();}+staticvoidupdate_head(conststructref*our,conststructref*remote,+constchar*msg)+{+if(our){+/* Local default branch link */+create_symref("HEAD",our->name,NULL);+if(!option_bare){+constchar*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);+}+}elseif(remote){+/* Source had detached HEAD pointing somewhere. */+update_ref(msg,"HEAD",remote->old_sha1,+NULL,REF_NODEREF,DIE_ON_ERR);+}+}+staticintcheckout(void){unsignedcharsha1[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){-constchar*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);-}-}elseif(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);
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 <redacted>
---
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(-)
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 <redacted>
---
builtin/clone.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
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 <redacted>
---
builtin/clone.c | 10 ++++------
t/t5706-clone-branch.sh | 8 ++------
2 files changed, 6 insertions(+), 12 deletions(-)
@@ -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);}elseour_head_points_at=remote_head_points_at;
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 <redacted>
---
Documentation/git-clone.txt | 5 +++--
builtin/clone.c | 13 +++++++++++++
t/t5601-clone.sh | 9 +++++++++
3 files changed, 25 insertions(+), 2 deletions(-)
@@ -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>::
@@ -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);}+}elseif(our){+structcommit*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);}elseif(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"),
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' 'grep/src/\\.git/objectstarget-10/objects/info/alternates'+test_expect_success'clone checking out a tag''+gitclone--branch=some-tagsrcdst.tag&&+GIT_DIR=src/.gitgitrev-parsesome-tag>expected&&+test_cmpexpecteddst.tag/.git/HEAD&&+GIT_DIR=dst.tag/.gitgitconfigremote.origin.fetch>fetch.actual&&+echo"+refs/heads/*:refs/remotes/origin/*">fetch.expected&&+test_cmpfetch.expectedfetch.actual+'+ test_done
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)error_resolve_conflict(me);die("Exiting because of an unresolved conflict.");}++voiddetach_advice(constchar*new_name)+{+constcharfmt[]=+"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);+}
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)strbuf_release(&sb);}-staticvoiddetach_advice(constchar*old_path,constchar*new_name)-{-constcharfmt[]=-"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);-}-staticvoidupdate_refs_for_switch(structcheckout_opts*opts,structbranch_info*old,structbranch_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);}}elseif(new->path){/* Switch branches. */
@@ -502,7 +502,10 @@ static int checkout(void)"unable to checkout.\n"));return0;}-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!"));}
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
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 <redacted>
---
builtin/clone.c | 9 +++------
t/t5601-clone.sh | 25 ++++++++++++++++++++++++-
2 files changed, 27 insertions(+), 7 deletions(-)
@@ -720,12 +720,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)}}elseif(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)
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 <redacted>
---
builtin/clone.c | 101 +++++++++++++++++++++++++++++++-----------------------
1 files changed, 58 insertions(+), 43 deletions(-)
@@ -448,6 +448,63 @@ static void write_remote_refs(const struct ref *local_refs)clear_extra_refs();}+staticintcheckout(void)+{+unsignedcharsha1[20];+char*head;+structlock_file*lock_file;+structunpack_trees_optionsopts;+structtree*tree;+structtree_desct;+interr=0,fd;++if(option_no_checkout)+return0;++head=resolve_refdup("HEAD",sha1,1,NULL);+if(!head){+warning(_("remote HEAD refers to nonexistent ref, "+"unable to checkout.\n"));+return0;+}+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(structlock_file));+fd=hold_locked_index(lock_file,1);++memset(&opts,0,sizeofopts);+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);++returnerr;+}+staticintwrite_one_config(constchar*key,constchar*value,void*data){returngit_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){-structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));-structunpack_trees_optionsopts;-structtree*tree;-structtree_desct;-intfd;--/* 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,sizeofopts);-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);
@@ -448,6 +448,29 @@ static void write_remote_refs(const struct ref *local_refs)clear_extra_refs();}+staticvoidupdate_head(conststructref*our,conststructref*remote,+constchar*msg)+{+if(our){+/* Local default branch link */+create_symref("HEAD",our->name,NULL);+if(!option_bare){+constchar*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);+}+}elseif(remote){+/*+*WeknowremoteHEADpointstoanon-branch,or+*HEADpointstoabranchbutwedon'tknowwhichone,or+*weaskedforaspecificbranchbutitdidnotexist.+*DetachHEADinallthesecases.+*/+update_ref(msg,"HEAD",remote->old_sha1,+NULL,REF_NODEREF,DIE_ON_ERR);+}+}+staticintcheckout(void){unsignedcharsha1[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){-constchar*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);-}-}elseif(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);
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 <redacted>
---
builtin/clone.c | 54 +++++++++++++++++++++++++++---------------------------
transport.c | 5 ++++-
2 files changed, 31 insertions(+), 28 deletions(-)
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 <redacted>
---
builtin/clone.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
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 <redacted>
---
builtin/clone.c | 13 +++++--------
t/t5706-clone-branch.sh | 8 ++------
2 files changed, 7 insertions(+), 14 deletions(-)
@@ -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);}elseour_head_points_at=remote_head_points_at;
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 <redacted>
---
Documentation/git-clone.txt | 5 +++--
builtin/clone.c | 13 +++++++++++++
t/t5601-clone.sh | 9 +++++++++
3 files changed, 25 insertions(+), 2 deletions(-)
@@ -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>::
@@ -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"),
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' 'grep/src/\\.git/objectstarget-10/objects/info/alternates'+test_expect_success'clone checking out a tag''+gitclone--branch=some-tagsrcdst.tag&&+GIT_DIR=src/.gitgitrev-parsesome-tag>expected&&+test_cmpexpecteddst.tag/.git/HEAD&&+GIT_DIR=dst.tag/.gitgitconfigremote.origin.fetch>fetch.actual&&+echo"+refs/heads/*:refs/remotes/origin/*">fetch.expected&&+test_cmpfetch.expectedfetch.actual+'+ test_done
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)error_resolve_conflict(me);die("Exiting because of an unresolved conflict.");}++voiddetach_advice(constchar*new_name)+{+constcharfmt[]=+"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);+}
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)strbuf_release(&sb);}-staticvoiddetach_advice(constchar*old_path,constchar*new_name)-{-constcharfmt[]=-"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);-}-staticvoidupdate_refs_for_switch(structcheckout_opts*opts,structbranch_info*old,structbranch_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);}}elseif(new->path){/* Switch branches. */
@@ -506,7 +506,10 @@ static int checkout(void)"unable to checkout.\n"));return0;}-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!"));}
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
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 <redacted>
---
builtin/clone.c | 9 +++------
t/t5601-clone.sh | 25 ++++++++++++++++++++++++-
2 files changed, 27 insertions(+), 7 deletions(-)
@@ -764,12 +764,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)}}elseif(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)
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 <redacted>
---
builtin/clone.c | 101 +++++++++++++++++++++++++++++++-----------------------
1 files changed, 58 insertions(+), 43 deletions(-)
@@ -486,6 +486,63 @@ static void write_followtags(const struct ref *refs, const char *msg)}}+staticintcheckout(void)+{+unsignedcharsha1[20];+char*head;+structlock_file*lock_file;+structunpack_trees_optionsopts;+structtree*tree;+structtree_desct;+interr=0,fd;++if(option_no_checkout)+return0;++head=resolve_refdup("HEAD",sha1,1,NULL);+if(!head){+warning(_("remote HEAD refers to nonexistent ref, "+"unable to checkout.\n"));+return0;+}+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(structlock_file));+fd=hold_locked_index(lock_file,1);++memset(&opts,0,sizeofopts);+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);++returnerr;+}+staticintwrite_one_config(constchar*key,constchar*value,void*data){returngit_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){-structlock_file*lock_file=xcalloc(1,sizeof(structlock_file));-structunpack_trees_optionsopts;-structtree*tree;-structtree_desct;-intfd;--/* 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,sizeofopts);-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);
@@ -486,6 +486,29 @@ static void write_followtags(const struct ref *refs, const char *msg)}}+staticvoidupdate_head(conststructref*our,conststructref*remote,+constchar*msg)+{+if(our){+/* Local default branch link */+create_symref("HEAD",our->name,NULL);+if(!option_bare){+constchar*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);+}+}elseif(remote){+/*+*WeknowremoteHEADpointstoanon-branch,or+*HEADpointstoabranchbutwedon'tknowwhichone,or+*weaskedforaspecificbranchbutitdidnotexist.+*DetachHEADinallthesecases.+*/+update_ref(msg,"HEAD",remote->old_sha1,+NULL,REF_NODEREF,DIE_ON_ERR);+}+}+staticintcheckout(void){unsignedcharsha1[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){-constchar*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);-}-}elseif(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);
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 <redacted>
---
builtin/clone.c | 54 +++++++++++++++++++++++++++---------------------------
transport.c | 5 ++++-
2 files changed, 31 insertions(+), 28 deletions(-)
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 <redacted>
---
builtin/clone.c | 30 ++++++++++++++++--------------
1 files changed, 16 insertions(+), 14 deletions(-)
@@ -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){-structstrbufhead=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 "
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 <redacted>
---
builtin/clone.c | 12 ++++--------
t/t5500-fetch-pack.sh | 7 -------
t/t5706-clone-branch.sh | 8 ++------
3 files changed, 6 insertions(+), 21 deletions(-)
@@ -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);}elseour_head_points_at=remote_head_points_at;
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 <redacted>
---
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(-)
@@ -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>::
@@ -271,4 +271,13 @@ test_expect_success 'clone from original with relative alternate' 'grep/src/\\.git/objectstarget-10/objects/info/alternates'+test_expect_success'clone checking out a tag''+gitclone--branch=some-tagsrcdst.tag&&+GIT_DIR=src/.gitgitrev-parsesome-tag>expected&&+test_cmpexpecteddst.tag/.git/HEAD&&+GIT_DIR=dst.tag/.gitgitconfigremote.origin.fetch>fetch.actual&&+echo"+refs/heads/*:refs/remotes/origin/*">fetch.expected&&+test_cmpfetch.expectedfetch.actual+'+ test_done
@@ -64,3 +64,17 @@ void NORETURN die_resolve_conflict(const char *me)error_resolve_conflict(me);die("Exiting because of an unresolved conflict.");}++voiddetach_advice(constchar*new_name)+{+constcharfmt[]=+"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);+}
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)strbuf_release(&sb);}-staticvoiddetach_advice(constchar*old_path,constchar*new_name)-{-constcharfmt[]=-"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);-}-staticvoidupdate_refs_for_switch(structcheckout_opts*opts,structbranch_info*old,structbranch_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);}}elseif(new->path){/* Switch branches. */
@@ -563,7 +563,10 @@ static int checkout(void)"unable to checkout.\n"));return0;}-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!"));}