Merge branch 'ef/maint-1.7.6-clone-progress-fix' into ef/maint-clone-progress-fix
authorJunio C Hamano <gitster@pobox.com>
Mon, 7 May 2012 19:35:36 +0000 (12:35 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 7 May 2012 19:35:36 +0000 (12:35 -0700)
By Erik Faye-Lund
* ef/maint-1.7.6-clone-progress-fix:
clone: fix progress-regression

1  2 
builtin/clone.c
diff --combined builtin/clone.c
index bbd5c96237fc332e159face6c8678d8ae3b9a3e9,87f2657171bcb0fc58f100ced8172cf2322797c6..a4d8d25ee319c2bbcfe5b450468cfb41d3fcd0d6
@@@ -37,7 -37,7 +37,7 @@@ static const char * const builtin_clone
        NULL
  };
  
 -static int option_no_checkout, option_bare, option_mirror;
 +static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
  static int option_local, option_no_hardlinks, option_shared, option_recursive;
  static char *option_template, *option_depth;
  static char *option_origin = NULL;
@@@ -45,8 -45,7 +45,8 @@@ static char *option_branch = NULL
  static const char *real_git_dir;
  static char *option_upload_pack = "git-upload-pack";
  static int option_verbosity;
 -static int option_progress;
 +static int option_progress = -1;
 +static struct string_list option_config;
  static struct string_list option_reference;
  
  static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
@@@ -60,8 -59,8 +60,8 @@@
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
 -      OPT_BOOLEAN(0, "progress", &option_progress,
 -                      "force progress reporting"),
 +      OPT_BOOL(0, "progress", &option_progress,
 +               "force progress reporting"),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
                   "directory from which templates will be used"),
        OPT_CALLBACK(0 , "reference", &option_reference, "repo",
                     "reference repository", &opt_parse_reference),
 -      OPT_STRING('o', "origin", &option_origin, "branch",
 -                 "use <branch> instead of 'origin' to track upstream"),
 +      OPT_STRING('o', "origin", &option_origin, "name",
 +                 "use <name> instead of 'origin' to track upstream"),
        OPT_STRING('b', "branch", &option_branch, "branch",
                   "checkout <branch> instead of the remote's HEAD"),
        OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
 +      OPT_BOOL(0, "single-branch", &option_single_branch,
 +                  "clone only one branch, HEAD or --branch"),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
 -
 +      OPT_STRING_LIST('c', "config", &option_config, "key=value",
 +                      "set config inside the new repository"),
        OPT_END()
  };
  
@@@ -107,7 -103,7 +107,7 @@@ static const char *argv_submodule[] = 
  
  static char *get_repo_path(const char *repo, int *is_bundle)
  {
 -      static char *suffix[] = { "/.git", ".git", "" };
 +      static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
        static char *bundle_suffix[] = { ".bundle", "" };
        struct stat st;
        int i;
                path = mkpath("%s%s", repo, suffix[i]);
                if (stat(path, &st))
                        continue;
 -              if (S_ISDIR(st.st_mode)) {
 +              if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
                        *is_bundle = 0;
                        return xstrdup(absolute_path(path));
                } else if (S_ISREG(st.st_mode) && st.st_size > 8) {
@@@ -232,6 -228,9 +232,6 @@@ static int add_one_reference(struct str
  {
        char *ref_git;
        struct strbuf alternate = STRBUF_INIT;
 -      struct remote *remote;
 -      struct transport *transport;
 -      const struct ref *extra;
  
        /* Beware: real_path() and mkpath() return static buffer */
        ref_git = xstrdup(real_path(item->string));
        strbuf_addf(&alternate, "%s/objects", ref_git);
        add_to_alternates_file(alternate.buf);
        strbuf_release(&alternate);
 -
 -      remote = remote_get(ref_git);
 -      transport = transport_get(remote, ref_git);
 -      for (extra = transport_get_remote_refs(transport); extra;
 -           extra = extra->next)
 -              add_extra_ref(extra->name, extra->old_sha1, 0);
 -
 -      transport_disconnect(transport);
        free(ref_git);
        return 0;
  }
@@@ -352,8 -359,13 +352,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;
@@@ -401,54 -418,14 +401,54 @@@ 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)
  {
 -      struct ref *local_refs = NULL;
 -      struct ref **tail = &local_refs;
 +      struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 +      struct ref *local_refs = head;
 +      struct ref **tail = head ? &head->next : &local_refs;
 +
 +      if (option_single_branch) {
 +              struct ref *remote_head = NULL;
 +
 +              if (!option_branch)
 +                      remote_head = guess_remote_head(head, refs, 0);
 +              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 {
 +                      get_fetch_map(remote_head, refspec, &tail, 0);
  
 -      get_fetch_map(refs, refspec, &tail, 0);
 -      if (!option_mirror)
 +                      /* 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);
 +
 +      if (!option_mirror && !option_single_branch)
                get_fetch_map(refs, tag_refspec, &tail, 0);
  
        return local_refs;
@@@ -458,153 -435,11 +458,153 @@@ static void write_remote_refs(const str
  {
        const struct ref *r;
  
 -      for (r = local_refs; r; r = r->next)
 -              add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
 +      for (r = local_refs; r; r = r->next) {
 +              if (!r->peer_ref)
 +                      continue;
 +              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)
 +{
 +      const struct ref *ref;
 +      for (ref = refs; ref; ref = ref->next) {
 +              if (prefixcmp(ref->name, "refs/tags/"))
 +                      continue;
 +              if (!suffixcmp(ref->name, "^{}"))
 +                      continue;
 +              if (!has_sha1_file(ref->old_sha1))
 +                      continue;
 +              update_ref(msg, ref->name, ref->old_sha1,
 +                         NULL, 0, DIE_ON_ERR);
 +      }
 +}
 +
 +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) {
 +              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.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);
 +}
 +
 +static void write_config(struct string_list *config)
 +{
 +      int i;
 +
 +      for (i = 0; i < config->nr; i++) {
 +              if (git_config_parse_parameter(config->items[i].string,
 +                                             write_one_config, NULL) < 0)
 +                      die("unable to write parameters to config file");
 +      }
  }
  
  int cmd_clone(int argc, const char **argv, const char *prefix)
        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;
 -      char *src_ref_prefix = "refs/heads/";
 -      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;
                usage_msg_opt(_("You must specify a repository to clone."),
                        builtin_clone_usage, builtin_clone_options);
  
 +      if (option_single_branch == -1)
 +              option_single_branch = option_depth ? 1 : 0;
 +
        if (option_mirror)
                option_bare = 1;
  
  
        if (0 <= option_verbosity) {
                if (option_bare)
 -                      printf(_("Cloning into bare repository %s...\n"), dir);
 +                      printf(_("Cloning into bare repository '%s'...\n"), dir);
                else
 -                      printf(_("Cloning into %s...\n"), dir);
 +                      printf(_("Cloning into '%s'...\n"), dir);
        }
        init_db(option_template, INIT_DB_QUIET);
 +      write_config(&option_config);
  
        /*
         * At this point, the config exists, so we do not need the
  
        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_depth)
                        transport_set_option(transport, TRANS_OPT_DEPTH,
                                             option_depth);
 +              if (option_single_branch)
 +                      transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
  
                transport_set_verbosity(transport, option_verbosity, option_progress);
  
                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);
 +
        if (refs) {
 -              clear_extra_refs();
 +              mapped_refs = wanted_peer_refs(refs, refspec);
 +              /*
 +               * 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;
 +                      }
  
 -              write_remote_refs(mapped_refs);
 +              if (!is_local && !complete_refs_before_fetch)
 +                      transport_fetch_refs(transport, mapped_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;
        }
        else {
                warning(_("You appear to have cloned an empty repository."));
 +              mapped_refs = NULL;
                our_head_points_at = NULL;
                remote_head_points_at = NULL;
                remote_head = NULL;
                                              "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);