Merge branch 'jk/clone-copy-alternates-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 17 Oct 2016 20:25:18 +0000 (13:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 17 Oct 2016 20:25:18 +0000 (13:25 -0700)
"git clone" of a local repository can be done at the filesystem
level, but the codepath did not check errors while copying and
adjusting the file that lists alternate object stores.

* jk/clone-copy-alternates-fix:
clone: detect errors in normalize_path_copy

1  2 
builtin/clone.c
diff --combined builtin/clone.c
index 6c80690adf32dfe55a3a587a85f36e279ccd150e,adc41027c6e7d4abed41ea7ad55f52ae93995bfc..6c76a6ed66fef567ca06e3e864b37fc3bed151d5
@@@ -40,22 -40,17 +40,22 @@@ static const char * const builtin_clone
  
  static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
  static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
 -static char *option_template, *option_depth;
 +static int option_shallow_submodules;
 +static int deepen;
 +static char *option_template, *option_depth, *option_since;
  static char *option_origin = NULL;
  static char *option_branch = NULL;
 +static struct string_list option_not = STRING_LIST_INIT_NODUP;
  static const char *real_git_dir;
  static char *option_upload_pack = "git-upload-pack";
  static int option_verbosity;
  static int option_progress = -1;
  static enum transport_family family;
 -static struct string_list option_config;
 -static struct string_list option_reference;
 +static struct string_list option_config = STRING_LIST_INIT_NODUP;
 +static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
 +static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
  static int option_dissociate;
 +static int max_jobs = -1;
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
                    N_("initialize submodules in the clone")),
        OPT_BOOL(0, "recurse-submodules", &option_recursive,
                    N_("initialize submodules in the clone")),
 +      OPT_INTEGER('j', "jobs", &max_jobs,
 +                  N_("number of submodules cloned in parallel")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
                   N_("directory from which templates will be used")),
 -      OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
 +      OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"),
                        N_("reference repository")),
 +      OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
 +                      N_("repo"), N_("reference repository")),
        OPT_BOOL(0, "dissociate", &option_dissociate,
                 N_("use --reference only while cloning")),
        OPT_STRING('o', "origin", &option_origin, N_("name"),
                   N_("path to git-upload-pack on the remote")),
        OPT_STRING(0, "depth", &option_depth, N_("depth"),
                    N_("create a shallow clone of that depth")),
 +      OPT_STRING(0, "shallow-since", &option_since, N_("time"),
 +                  N_("create a shallow clone since a specific time")),
 +      OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"),
 +                      N_("deepen history of shallow clone by excluding rev")),
        OPT_BOOL(0, "single-branch", &option_single_branch,
                    N_("clone only one branch, HEAD or --branch")),
 +      OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules,
 +                  N_("any cloned submodules will be shallow")),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
                   N_("separate git dir from working tree")),
        OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
        OPT_END()
  };
  
 -static const char *argv_submodule[] = {
 -      "submodule", "update", "--init", "--recursive", NULL
 -};
 -
  static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
  {
        static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
@@@ -291,37 -280,50 +291,37 @@@ static void strip_trailing_slashes(cha
  
  static int add_one_reference(struct string_list_item *item, void *cb_data)
  {
 -      char *ref_git;
 -      const char *repo;
 -      struct strbuf alternate = STRBUF_INIT;
 -
 -      /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
 -      ref_git = xstrdup(real_path(item->string));
 -
 -      repo = read_gitfile(ref_git);
 -      if (!repo)
 -              repo = read_gitfile(mkpath("%s/.git", ref_git));
 -      if (repo) {
 -              free(ref_git);
 -              ref_git = xstrdup(repo);
 -      }
 +      struct strbuf err = STRBUF_INIT;
 +      int *required = cb_data;
 +      char *ref_git = compute_alternate_path(item->string, &err);
  
 -      if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
 -              char *ref_git_git = mkpathdup("%s/.git", ref_git);
 -              free(ref_git);
 -              ref_git = ref_git_git;
 -      } else if (!is_directory(mkpath("%s/objects", ref_git))) {
 +      if (!ref_git) {
 +              if (*required)
 +                      die("%s", err.buf);
 +              else
 +                      fprintf(stderr,
 +                              _("info: Could not add alternate for '%s': %s\n"),
 +                              item->string, err.buf);
 +      } else {
                struct strbuf sb = STRBUF_INIT;
 -              if (get_common_dir(&sb, ref_git))
 -                      die(_("reference repository '%s' as a linked checkout is not supported yet."),
 -                          item->string);
 -              die(_("reference repository '%s' is not a local repository."),
 -                  item->string);
 +              strbuf_addf(&sb, "%s/objects", ref_git);
 +              add_to_alternates_file(sb.buf);
 +              strbuf_release(&sb);
        }
  
 -      if (!access(mkpath("%s/shallow", ref_git), F_OK))
 -              die(_("reference repository '%s' is shallow"), item->string);
 -
 -      if (!access(mkpath("%s/info/grafts", ref_git), F_OK))
 -              die(_("reference repository '%s' is grafted"), item->string);
 -
 -      strbuf_addf(&alternate, "%s/objects", ref_git);
 -      add_to_alternates_file(alternate.buf);
 -      strbuf_release(&alternate);
 +      strbuf_release(&err);
        free(ref_git);
        return 0;
  }
  
  static void setup_reference(void)
  {
 -      for_each_string_list(&option_reference, add_one_reference, NULL);
 +      int required = 1;
 +      for_each_string_list(&option_required_reference,
 +                           add_one_reference, &required);
 +      required = 0;
 +      for_each_string_list(&option_optional_reference,
 +                           add_one_reference, &required);
  }
  
  static void copy_alternates(struct strbuf *src, struct strbuf *dst,
                        continue;
                }
                abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
-               normalize_path_copy(abs_path, abs_path);
-               add_to_alternates_file(abs_path);
+               if (!normalize_path_copy(abs_path, abs_path))
+                       add_to_alternates_file(abs_path);
+               else
+                       warning("skipping invalid relative alternate: %s/%s",
+                               src_repo, line.buf);
                free(abs_path);
        }
        strbuf_release(&line);
@@@ -620,13 -625,13 +623,13 @@@ static void update_remote_refs(const st
        const struct ref *rm = mapped_refs;
  
        if (check_connectivity) {
 -              if (transport->progress)
 -                      fprintf(stderr, _("Checking connectivity... "));
 -              if (check_everything_connected_with_transport(iterate_ref_map,
 -                                                            0, &rm, transport))
 +              struct check_connected_options opt = CHECK_CONNECTED_INIT;
 +
 +              opt.transport = transport;
 +              opt.progress = transport->progress;
 +
 +              if (check_connected(iterate_ref_map, &rm, &opt))
                        die(_("remote did not send all necessary objects"));
 -              if (transport->progress)
 -                      fprintf(stderr, _("done.\n"));
        }
  
        if (refs) {
@@@ -676,7 -681,7 +679,7 @@@ static void update_head(const struct re
        }
  }
  
 -static int checkout(void)
 +static int checkout(int submodule_progress)
  {
        unsigned char sha1[20];
        char *head;
        err |= run_hook_le(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);
 +      if (!err && option_recursive) {
 +              struct argv_array args = ARGV_ARRAY_INIT;
 +              argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
 +
 +              if (option_shallow_submodules == 1)
 +                      argv_array_push(&args, "--depth=1");
 +
 +              if (max_jobs != -1)
 +                      argv_array_pushf(&args, "--jobs=%d", max_jobs);
 +
 +              if (submodule_progress)
 +                      argv_array_push(&args, "--progress");
 +
 +              err = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +              argv_array_clear(&args);
 +      }
  
        return err;
  }
@@@ -850,7 -841,6 +853,7 @@@ int cmd_clone(int argc, const char **ar
        const char *src_ref_prefix = "refs/heads/";
        struct remote *remote;
        int err = 0, complete_refs_before_fetch = 1;
 +      int submodule_progress;
  
        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_depth || option_since || option_not.nr)
 +              deepen = 1;
        if (option_single_branch == -1)
 -              option_single_branch = option_depth ? 1 : 0;
 +              option_single_branch = deepen ? 1 : 0;
  
        if (option_mirror)
                option_bare = 1;
                set_git_work_tree(work_tree);
        }
  
 -      junk_git_dir = git_dir;
 +      junk_git_dir = real_git_dir ? real_git_dir : git_dir;
        if (safe_create_leading_directories_const(git_dir) < 0)
                die(_("could not create leading directories of '%s'"), git_dir);
  
 -      set_git_dir_init(git_dir, real_git_dir, 0);
 -      if (real_git_dir) {
 -              git_dir = real_git_dir;
 -              junk_git_dir = real_git_dir;
 -      }
 -
        if (0 <= option_verbosity) {
                if (option_bare)
                        fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
                else
                        fprintf(stderr, _("Cloning into '%s'...\n"), dir);
        }
 -      init_db(option_template, INIT_DB_QUIET);
 +
 +      if (option_recursive) {
 +              if (option_required_reference.nr &&
 +                  option_optional_reference.nr)
 +                      die(_("clone --recursive is not compatible with "
 +                            "both --reference and --reference-if-able"));
 +              else if (option_required_reference.nr) {
 +                      string_list_append(&option_config,
 +                              "submodule.alternateLocation=superproject");
 +                      string_list_append(&option_config,
 +                              "submodule.alternateErrorStrategy=die");
 +              } else if (option_optional_reference.nr) {
 +                      string_list_append(&option_config,
 +                              "submodule.alternateLocation=superproject");
 +                      string_list_append(&option_config,
 +                              "submodule.alternateErrorStrategy=info");
 +              }
 +      }
 +
 +      init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
 +
 +      if (real_git_dir)
 +              git_dir = real_git_dir;
 +
        write_config(&option_config);
  
        git_config(git_default_config, NULL);
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
  
 -      if (option_reference.nr)
 +      if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
        fetch_pattern = value.buf;
        if (is_local) {
                if (option_depth)
                        warning(_("--depth is ignored in local clones; use file:// instead."));
 +              if (option_since)
 +                      warning(_("--shallow-since is ignored in local clones; use file:// instead."));
 +              if (option_not.nr)
 +                      warning(_("--shallow-exclude is ignored in local clones; use file:// instead."));
                if (!access(mkpath("%s/shallow", path), F_OK)) {
                        if (option_local > 0)
                                warning(_("source repository is shallow, ignoring --local"));
        if (option_depth)
                transport_set_option(transport, TRANS_OPT_DEPTH,
                                     option_depth);
 +      if (option_since)
 +              transport_set_option(transport, TRANS_OPT_DEEPEN_SINCE,
 +                                   option_since);
 +      if (option_not.nr)
 +              transport_set_option(transport, TRANS_OPT_DEEPEN_NOT,
 +                                   (const char *)&option_not);
        if (option_single_branch)
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
  
                transport_set_option(transport, TRANS_OPT_UPLOADPACK,
                                     option_upload_pack);
  
 -      if (transport->smart_options && !option_depth)
 +      if (transport->smart_options && !deepen)
                transport->smart_options->check_self_contained_and_connected = 1;
  
        refs = transport_get_remote_refs(transport);
  
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
  
 +      /*
 +       * We want to show progress for recursive submodule clones iff
 +       * we did so for the main clone. But only the transport knows
 +       * the final decision for this flag, so we need to rescue the value
 +       * before we free the transport.
 +       */
 +      submodule_progress = transport->progress;
 +
        transport_unlock_pack(transport);
        transport_disconnect(transport);
  
        }
  
        junk_mode = JUNK_LEAVE_REPO;
 -      err = checkout();
 +      err = checkout(submodule_progress);
  
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);