Merge branch 'nd/clone-detached'
authorJunio C Hamano <gitster@pobox.com>
Wed, 1 Feb 2012 06:24:23 +0000 (22:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 1 Feb 2012 06:24:23 +0000 (22:24 -0800)
* nd/clone-detached:
clone: fix up delay cloning conditions
push: do not let configured foreign-vcs permanently clobbered
clone: print advice on checking out detached HEAD
clone: allow --branch to take a tag
clone: refuse to clone if --branch points to bogus ref
clone: --branch=<branch> always means refs/heads/<branch>
clone: delay cloning until after remote HEAD checking
clone: factor out remote ref writing
clone: factor out HEAD update code
clone: factor out checkout code
clone: write detached HEAD in bare repositories
t5601: add missing && cascade

1  2 
advice.c
builtin/clone.c
diff --combined advice.c
index 65a07859f261e12e88d638a1f4546583e0133d4c,3e1a145bf0b7eb482246f965da0997ab42706199..01130e54e7b270df7f535fb815dba25ddb72ec1a
+++ b/advice.c
@@@ -21,21 -21,11 +21,21 @@@ static struct 
  
  void advise(const char *advice, ...)
  {
 +      struct strbuf buf = STRBUF_INIT;
        va_list params;
 +      const char *cp, *np;
  
        va_start(params, advice);
 -      vreportf("hint: ", advice, params);
 +      strbuf_addf(&buf, advice, params);
        va_end(params);
 +
 +      for (cp = buf.buf; *cp; cp = np) {
 +              np = strchrnul(cp, '\n');
 +              fprintf(stderr, _("hint: %.*s\n"), (int)(np - cp), cp);
 +              if (*np)
 +                      np++;
 +      }
 +      strbuf_release(&buf);
  }
  
  int git_default_advice_config(const char *var, const char *value)
  int error_resolve_conflict(const char *me)
  {
        error("'%s' is not possible because you have unmerged files.", me);
 -      if (advice_resolve_conflict) {
 +      if (advice_resolve_conflict)
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
                 */
 -              advise("Fix them up in the work tree,");
 -              advise("and then use 'git add/rm <file>' as");
 -              advise("appropriate to mark resolution and make a commit,");
 -              advise("or use 'git commit -a'.");
 -      }
 +              advise(_("Fix them up in the work tree,\n"
 +                       "and then use 'git add/rm <file>' as\n"
 +                       "appropriate to mark resolution and make a commit,\n"
 +                       "or use 'git commit -a'."));
        return -1;
  }
  
@@@ -73,3 -64,17 +73,17 @@@ void NORETURN die_resolve_conflict(cons
        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 --combined builtin/clone.c
index 9084febb1428a4232b43d97912c7e3e7dfb9e382,379cafa63faf2300c9564cde312c02a11c01f593..c62d4b5737ed41f3c7a2d9cbec3b77651236a467
@@@ -48,7 -48,6 +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)
  {
@@@ -364,13 -363,8 +363,8 @@@ static void copy_or_link_directory(stru
        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);
                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;
@@@ -423,6 -412,26 +412,26 @@@ static void remove_junk_on_signal(int s
        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);
+       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;
+ }
  static struct ref *wanted_peer_refs(const struct ref *refs,
                struct refspec *refspec)
  {
  
                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."),
                                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);
  
@@@ -464,10 -472,11 +472,10 @@@ static void write_remote_refs(const str
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
 -              add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
 +              add_packed_ref(r->peer_ref->name, r->old_sha1);
        }
  
        pack_refs(PACK_REFS_ALL);
 -      clear_extra_refs();
  }
  
  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)
+ {
+       if (our && !prefixcmp(our->name, "refs/heads/")) {
+               /* 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 (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
+                * 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,
+                          NULL, REF_NODEREF, DIE_ON_ERR);
+       }
+ }
+ 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 (advice_detached_head)
+                       detach_advice(sha1_to_hex(sha1));
+       } else {
+               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);
@@@ -512,10 -631,13 +630,13 @@@ int cmd_clone(int argc, const char **ar
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
        struct ref *mapped_refs;
+       const struct ref *ref;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
-       int err = 0;
+       const char *src_ref_prefix = "refs/heads/";
+       struct remote *remote;
+       int err = 0, complete_refs_before_fetch = 1;
  
        struct refspec *refspec;
        const char *fetch_pattern;
  
        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);
  
                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);
-               }
        }
  
-       if (refs) {
-               clear_extra_refs();
+       refs = transport_get_remote_refs(transport);
+       mapped_refs = refs ? wanted_peer_refs(refs, refspec) : NULL;
  
-               write_remote_refs(mapped_refs);
-               if (option_single_branch)
-                       write_followtags(refs, reflog_msg.buf);
+       /*
+        * transport_get_remote_refs() may return refs with null sha-1
+        * in mapped_refs (see struct transport->get_refs_list
+        * comment). In that case 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.
+        */
+       for (ref = refs; ref; ref = ref->next)
+               if (is_null_sha1(ref->old_sha1)) {
+                       complete_refs_before_fetch = 0;
+                       break;
+               }
+       if (!is_local && !complete_refs_before_fetch && refs)
+               transport_fetch_refs(transport, mapped_refs);
  
+       if (refs) {
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
                        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);
-                       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;
-                       }
+                               find_remote_branch(mapped_refs, option_branch);
+                       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;
                                              "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);
-       }
+       if (is_local)
+               clone_local(path, git_dir);
+       else if (refs && complete_refs_before_fetch)
+               transport_fetch_refs(transport, mapped_refs);
  
-       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. */
-               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;
-               }
-       } else {
-               /* Nothing to checkout out */
-               if (!option_no_checkout)
-                       warning(_("remote HEAD refers to nonexistent ref, "
-                               "unable to checkout.\n"));
-               option_no_checkout = 1;
-       }
+       update_remote_refs(refs, mapped_refs, remote_head_points_at,
+                          branch_top.buf, reflog_msg.buf);
  
-       if (transport) {
-               transport_unlock_pack(transport);
-               transport_disconnect(transport);
-       }
+       update_head(our_head_points_at, remote_head, reflog_msg.buf);
  
-       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);
-       }
+       transport_unlock_pack(transport);
+       transport_disconnect(transport);
+       err = checkout();
  
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);