Merge branch 'es/clone-shared-worktree'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Dec 2017 19:16:28 +0000 (11:16 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Dec 2017 19:16:28 +0000 (11:16 -0800)
"git clone --shared" to borrow from a (secondary) worktree did not
work, even though "git clone --local" did. Both are now accepted.

* es/clone-shared-worktree:
clone: support 'clone --shared' from a worktree

1  2 
builtin/clone.c
t/t2025-worktree-add.sh
diff --combined builtin/clone.c
index b22845738afe68e61d45883e89a470213de921ef,69eabc0d8802804d59bd03d443bdcd8b8a1da750..6ad0ab3fa40af1761482f5fc78b4b1c401f9717c
@@@ -9,7 -9,6 +9,7 @@@
   */
  
  #include "builtin.h"
 +#include "config.h"
  #include "lockfile.h"
  #include "parse-options.h"
  #include "fetch-pack.h"
@@@ -25,7 -24,6 +25,7 @@@
  #include "remote.h"
  #include "run-command.h"
  #include "connected.h"
 +#include "packfile.h"
  
  /*
   * Overall FIXMEs:
@@@ -41,39 -39,17 +41,39 @@@ 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_local = -1, option_no_hardlinks, option_shared;
 +static int option_no_tags;
 +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 struct string_list option_config;
 -static struct string_list option_reference;
 +static enum transport_family family;
 +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 string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
 +
 +static int recurse_submodules_cb(const struct option *opt,
 +                               const char *arg, int unset)
 +{
 +      if (unset)
 +              string_list_clear((struct string_list *)opt->value, 0);
 +      else if (arg)
 +              string_list_append((struct string_list *)opt->value, arg);
 +      else
 +              string_list_append((struct string_list *)opt->value,
 +                                 (const char *)opt->defval);
 +
 +      return 0;
 +}
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
                    N_("don't use local hardlinks, always copy")),
        OPT_BOOL('s', "shared", &option_shared,
                    N_("setup as shared repository")),
 -      OPT_BOOL(0, "recursive", &option_recursive,
 -                  N_("initialize submodules in the clone")),
 -      OPT_BOOL(0, "recurse-submodules", &option_recursive,
 -                  N_("initialize submodules in the clone")),
 +      { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules,
 +        N_("pathspec"), N_("initialize submodules in the clone"),
 +        PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb,
 +        (intptr_t)"." },
 +      { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
 +        N_("pathspec"), N_("initialize submodules in the clone"),
 +        PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
 +      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, excluding rev")),
        OPT_BOOL(0, "single-branch", &option_single_branch,
                    N_("clone only one branch, HEAD or --branch")),
 +      OPT_BOOL(0, "no-tags", &option_no_tags,
 +               N_("don't clone any tags, and make later fetches not to follow them")),
 +      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"),
                        N_("set config inside the new repository")),
 +      OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
 +                      TRANSPORT_FAMILY_IPV4),
 +      OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
 +                      TRANSPORT_FAMILY_IPV6),
        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" };
@@@ -193,7 -154,7 +193,7 @@@ static char *get_repo_path(const char *
  
        strbuf_addstr(&path, repo);
        raw = get_repo_path_1(&path, is_bundle);
 -      canon = raw ? xstrdup(absolute_path(raw)) : NULL;
 +      canon = raw ? absolute_pathdup(raw) : NULL;
        strbuf_release(&path);
        return canon;
  }
@@@ -270,8 -231,8 +270,8 @@@ static char *guess_dir_name(const char 
        strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
  
        if (!len || (len == 1 && *start == '/'))
 -          die("No directory name could be guessed.\n"
 -              "Please specify a directory on the command line");
 +              die(_("No directory name could be guessed.\n"
 +                    "Please specify a directory on the command line"));
  
        if (is_bare)
                dir = xstrfmt("%.*s.git", (int)len, start);
@@@ -314,37 -275,50 +314,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,
         * to turn entries with paths relative to the original
         * absolute, so that they can be used in the new repository.
         */
 -      FILE *in = fopen(src->buf, "r");
 +      FILE *in = xfopen(src->buf, "r");
        struct strbuf line = STRBUF_INIT;
  
 -      while (strbuf_getline(&line, in, '\n') != EOF) {
 +      while (strbuf_getline(&line, in) != EOF) {
                char *abs_path;
                if (!line.len || line.buf[0] == '#')
                        continue;
                        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);
@@@ -452,7 -423,8 +452,8 @@@ static void clone_local(const char *src
  {
        if (option_shared) {
                struct strbuf alt = STRBUF_INIT;
-               strbuf_addf(&alt, "%s/objects", src_repo);
+               get_common_dir(&alt, src_repo);
+               strbuf_addstr(&alt, "/objects");
                add_to_alternates_file(alt.buf);
                strbuf_release(&alt);
        } else {
@@@ -507,8 -479,8 +508,8 @@@ static void remove_junk(void
        if (junk_work_tree) {
                strbuf_addstr(&sb, junk_work_tree);
                remove_dir_recursively(&sb, 0);
 -              strbuf_reset(&sb);
        }
 +      strbuf_release(&sb);
  }
  
  static void remove_junk_on_signal(int signo)
@@@ -568,7 -540,7 +569,7 @@@ static struct ref *wanted_peer_refs(con
        } else
                get_fetch_map(refs, refspec, &tail, 0);
  
 -      if (!option_mirror && !option_single_branch)
 +      if (!option_mirror && !option_single_branch && !option_no_tags)
                get_fetch_map(refs, tag_refspec, &tail, 0);
  
        return local_refs;
@@@ -588,7 -560,7 +589,7 @@@ static void write_remote_refs(const str
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
 -              if (ref_transaction_create(t, r->peer_ref->name, r->old_sha1,
 +              if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,
                                           0, NULL, &err))
                        die("%s", err.buf);
        }
@@@ -608,14 -580,14 +609,14 @@@ static void write_followtags(const stru
                        continue;
                if (ends_with(ref->name, "^{}"))
                        continue;
 -              if (!has_sha1_file(ref->old_sha1))
 +              if (!has_object_file(&ref->old_oid))
                        continue;
 -              update_ref(msg, ref->name, ref->old_sha1,
 -                         NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 +              update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
 +                         UPDATE_REFS_DIE_ON_ERR);
        }
  }
  
 -static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
 +static int iterate_ref_map(void *cb_data, struct object_id *oid)
  {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
        if (!ref)
                return -1;
  
 -      hashcpy(sha1, ref->old_sha1);
 +      oidcpy(oid, &ref->old_oid);
        *rm = ref->next;
        return 0;
  }
@@@ -646,18 -618,18 +647,18 @@@ 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) {
                write_remote_refs(mapped_refs);
 -              if (option_single_branch)
 +              if (option_single_branch && !option_no_tags)
                        write_followtags(refs, msg);
        }
  
                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);
 +              if (create_symref(head_ref.buf,
 +                                remote_head_points_at->peer_ref->name,
 +                                msg) < 0)
 +                      die(_("unable to update %s"), head_ref.buf);
 +              strbuf_release(&head_ref);
        }
  }
  
@@@ -679,34 -649,33 +680,34 @@@ static void update_head(const struct re
        const char *head;
        if (our && skip_prefix(our->name, "refs/heads/", &head)) {
                /* Local default branch link */
 -              create_symref("HEAD", our->name, NULL);
 +              if (create_symref("HEAD", our->name, NULL) < 0)
 +                      die(_("unable to update HEAD"));
                if (!option_bare) {
 -                      update_ref(msg, "HEAD", our->old_sha1, NULL, 0,
 +                      update_ref(msg, "HEAD", &our->old_oid, NULL, 0,
                                   UPDATE_REFS_DIE_ON_ERR);
                        install_branch_config(0, head, option_origin, our->name);
                }
        } else if (our) {
 -              struct commit *c = lookup_commit_reference(our->old_sha1);
 +              struct commit *c = lookup_commit_reference(&our->old_oid);
                /* --branch specifies a non-branch (i.e. tags), detach HEAD */
 -              update_ref(msg, "HEAD", c->object.sha1,
 -                         NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
 +              update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
 +                         UPDATE_REFS_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, UPDATE_REFS_DIE_ON_ERR);
 +              update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF,
 +                         UPDATE_REFS_DIE_ON_ERR);
        }
  }
  
 -static int checkout(void)
 +static int checkout(int submodule_progress)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        char *head;
 -      struct lock_file *lock_file;
 +      struct lock_file lock_file = LOCK_INIT;
        struct unpack_trees_options opts;
        struct tree *tree;
        struct tree_desc t;
        if (option_no_checkout)
                return 0;
  
 -      head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
 +      head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
        }
        if (!strcmp(head, "HEAD")) {
                if (advice_detached_head)
 -                      detach_advice(sha1_to_hex(sha1));
 +                      detach_advice(oid_to_hex(&oid));
        } else {
                if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        /* We need to be in the new work tree for the checkout */
        setup_work_tree();
  
 -      lock_file = xcalloc(1, sizeof(struct lock_file));
 -      hold_locked_index(lock_file, 1);
 +      hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
  
        memset(&opts, 0, sizeof opts);
        opts.update = 1;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
  
 -      tree = parse_tree_indirect(sha1);
 +      tree = parse_tree_indirect(&oid);
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
        if (unpack_trees(1, &t, &opts) < 0)
                die(_("unable to checkout working tree"));
  
 -      if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
 +      if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
  
        err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
 -                         sha1_to_hex(sha1), "1", NULL);
 +                         oid_to_hex(&oid), "1", NULL);
  
 -      if (!err && option_recursive)
 -              err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
 +      if (!err && (option_recurse_submodules.nr > 0)) {
 +              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");
 +
 +              if (option_verbosity < 0)
 +                      argv_array_push(&args, "--quiet");
 +
 +              err = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +              argv_array_clear(&args);
 +      }
  
        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);
 +      return git_config_set_multivar_gently(key,
 +                                            value ? value : "true",
 +                                            CONFIG_REGEX_NONE, 0);
  }
  
  static void write_config(struct string_list *config)
        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");
 +                      die(_("unable to write parameters to config file"));
        }
  }
  
@@@ -851,15 -802,11 +852,15 @@@ static void write_refspec_config(const 
  static void dissociate_from_references(void)
  {
        static const char* argv[] = { "repack", "-a", "-d", NULL };
 +      char *alternates = git_pathdup("objects/info/alternates");
  
 -      if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
 -              die(_("cannot repack to clean up"));
 -      if (unlink(git_path("objects/info/alternates")) && errno != ENOENT)
 -              die_errno(_("cannot unlink temporary alternates file"));
 +      if (!access(alternates, F_OK)) {
 +              if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
 +                      die(_("cannot repack to clean up"));
 +              if (unlink(alternates) && errno != ENOENT)
 +                      die_errno(_("cannot unlink temporary alternates file"));
 +      }
 +      free(alternates);
  }
  
  int cmd_clone(int argc, const char **argv, const char *prefix)
        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;
  
        path = get_repo_path(repo_name, &is_bundle);
        if (path)
 -              repo = xstrdup(absolute_path(repo_name));
 +              repo = absolute_pathdup(repo_name);
        else if (!strchr(repo_name, ':'))
                die(_("repository '%s' does not exist"), repo_name);
        else
                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_recurse_submodules.nr > 0) {
 +              struct string_list_item *item;
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              /* remove duplicates */
 +              string_list_sort(&option_recurse_submodules);
 +              string_list_remove_duplicates(&option_recurse_submodules, 0);
 +
 +              /*
 +               * NEEDSWORK: In a multi-working-tree world, this needs to be
 +               * set in the per-worktree config.
 +               */
 +              for_each_string_list_item(item, &option_recurse_submodules) {
 +                      strbuf_addf(&sb, "submodule.active=%s",
 +                                  item->string);
 +                      string_list_append(&option_config,
 +                                         strbuf_detach(&sb, NULL));
 +              }
 +
 +              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)
 -              setup_reference();
 -      else if (option_dissociate) {
 -              warning(_("--dissociate given, but there is no --reference"));
 -              option_dissociate = 0;
 +      if (option_no_tags) {
 +              strbuf_addf(&key, "remote.%s.tagOpt", option_origin);
 +              git_config_set(key.buf, "--no-tags");
 +              strbuf_reset(&key);
        }
  
 +      if (option_required_reference.nr || option_optional_reference.nr)
 +              setup_reference();
 +
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
  
        remote = remote_get(option_origin);
        transport = transport_get(remote, remote->url[0]);
        transport_set_verbosity(transport, option_verbosity, option_progress);
 +      transport->family = family;
  
        path = get_repo_path(remote->url[0], &is_bundle);
        is_local = option_local != 0 && path && !is_bundle;
        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);
                 * remote HEAD check.
                 */
                for (ref = refs; ref; ref = ref->next)
 -                      if (is_null_sha1(ref->old_sha1)) {
 +                      if (is_null_oid(&ref->old_oid)) {
                                complete_refs_before_fetch = 0;
                                break;
                        }
  
        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);
  
 -      if (option_dissociate)
 +      if (option_dissociate) {
 +              close_all_packs();
                dissociate_from_references();
 +      }
  
        junk_mode = JUNK_LEAVE_REPO;
 -      err = checkout();
 +      err = checkout(submodule_progress);
  
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
diff --combined t/t2025-worktree-add.sh
index 1285668cfce0bb0f9f7a62dcf3dd293d03be40b2,8f3e726d351b37fdd4f6e1cb9cb1d2f6a9400ea6..2b959449730e14dd4e650ce9115a463d71eedca4
@@@ -4,8 -4,6 +4,8 @@@ test_description='test git worktree add
  
  . ./test-lib.sh
  
 +. "$TEST_DIRECTORY"/lib-rebase.sh
 +
  test_expect_success 'setup' '
        test_commit init
  '
@@@ -20,22 -18,6 +20,22 @@@ test_expect_success '"add" an existing 
        git worktree add --detach existing_empty master
  '
  
 +test_expect_success '"add" using shorthand - fails when no previous branch' '
 +      test_must_fail git worktree add existing_short -
 +'
 +
 +test_expect_success '"add" using - shorthand' '
 +      git checkout -b newbranch &&
 +      echo hello >myworld &&
 +      git add myworld &&
 +      git commit -m myworld &&
 +      git checkout master &&
 +      git worktree add short-hand - &&
 +      echo refs/heads/newbranch >expect &&
 +      git -C short-hand rev-parse --symbolic-full-name HEAD >actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success '"add" refuses to checkout locked branch' '
        test_must_fail git worktree add zere master &&
        ! test -d zere &&
@@@ -63,12 -45,6 +63,12 @@@ test_expect_success '"add" worktree' 
        )
  '
  
 +test_expect_success '"add" worktree with lock' '
 +      git rev-parse HEAD >expect &&
 +      git worktree add --detach --lock here-with-lock master &&
 +      test -f .git/worktrees/here-with-lock/locked
 +'
 +
  test_expect_success '"add" worktree from a subdir' '
        (
                mkdir sub &&
@@@ -144,21 -120,13 +144,21 @@@ test_expect_success 'checkout from a ba
        )
  '
  
 +test_expect_success '"add" default branch of a bare repo' '
 +      (
 +              git clone --bare . bare2 &&
 +              cd bare2 &&
 +              git worktree add ../there3 master
 +      )
 +'
 +
  test_expect_success 'checkout with grafts' '
        test_when_finished rm .git/info/grafts &&
        test_commit abc &&
 -      SHA1=`git rev-parse HEAD` &&
 +      SHA1=$(git rev-parse HEAD) &&
        test_commit def &&
        test_commit xyz &&
 -      echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
 +      echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts &&
        cat >expected <<-\EOF &&
        xyz
        abc
@@@ -225,252 -193,15 +225,258 @@@ test_expect_success '"add" -B/--detach 
        test_must_fail git worktree add -B poodle --detach bamboo master
  '
  
 +test_expect_success '"add -B" fails if the branch is checked out' '
 +      git rev-parse newmaster >before &&
 +      test_must_fail git worktree add -B newmaster bamboo master &&
 +      git rev-parse newmaster >after &&
 +      test_cmp before after
 +'
 +
 +test_expect_success 'add -B' '
 +      git worktree add -B poodle bamboo2 master^ &&
 +      git -C bamboo2 symbolic-ref HEAD >actual &&
 +      echo refs/heads/poodle >expected &&
 +      test_cmp expected actual &&
 +      test_cmp_rev master^ poodle
 +'
 +
  test_expect_success 'local clone from linked checkout' '
        git clone --local here here-clone &&
        ( cd here-clone && git fsck )
  '
  
+ test_expect_success 'local clone --shared from linked checkout' '
+       git -C bare worktree add --detach ../baretree &&
+       git clone --local --shared baretree bare-clone &&
+       grep /bare/ bare-clone/.git/objects/info/alternates
+ '
 +test_expect_success '"add" worktree with --no-checkout' '
 +      git worktree add --no-checkout -b swamp swamp &&
 +      ! test -e swamp/init.t &&
 +      git -C swamp reset --hard &&
 +      test_cmp init.t swamp/init.t
 +'
 +
 +test_expect_success '"add" worktree with --checkout' '
 +      git worktree add --checkout -b swmap2 swamp2 &&
 +      test_cmp init.t swamp2/init.t
 +'
 +
 +test_expect_success 'put a worktree under rebase' '
 +      git worktree add under-rebase &&
 +      (
 +              cd under-rebase &&
 +              set_fake_editor &&
 +              FAKE_LINES="edit 1" git rebase -i HEAD^ &&
 +              git worktree list | grep "under-rebase.*detached HEAD"
 +      )
 +'
 +
 +test_expect_success 'add a worktree, checking out a rebased branch' '
 +      test_must_fail git worktree add new-rebase under-rebase &&
 +      ! test -d new-rebase
 +'
 +
 +test_expect_success 'checking out a rebased branch from another worktree' '
 +      git worktree add new-place &&
 +      test_must_fail git -C new-place checkout under-rebase
 +'
 +
 +test_expect_success 'not allow to delete a branch under rebase' '
 +      (
 +              cd under-rebase &&
 +              test_must_fail git branch -D under-rebase
 +      )
 +'
 +
 +test_expect_success 'rename a branch under rebase not allowed' '
 +      test_must_fail git branch -M under-rebase rebase-with-new-name
 +'
 +
 +test_expect_success 'check out from current worktree branch ok' '
 +      (
 +              cd under-rebase &&
 +              git checkout under-rebase &&
 +              git checkout - &&
 +              git rebase --abort
 +      )
 +'
 +
 +test_expect_success 'checkout a branch under bisect' '
 +      git worktree add under-bisect &&
 +      (
 +              cd under-bisect &&
 +              git bisect start &&
 +              git bisect bad &&
 +              git bisect good HEAD~2 &&
 +              git worktree list | grep "under-bisect.*detached HEAD" &&
 +              test_must_fail git worktree add new-bisect under-bisect &&
 +              ! test -d new-bisect
 +      )
 +'
 +
 +test_expect_success 'rename a branch under bisect not allowed' '
 +      test_must_fail git branch -M under-bisect bisect-with-new-name
 +'
 +# Is branch "refs/heads/$1" set to pull from "$2/$3"?
 +test_branch_upstream () {
 +      printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
 +      {
 +              git config "branch.$1.remote" &&
 +              git config "branch.$1.merge"
 +      } >actual.upstream &&
 +      test_cmp expect.upstream actual.upstream
 +}
 +
 +test_expect_success '--track sets up tracking' '
 +      test_when_finished rm -rf track &&
 +      git worktree add --track -b track track master &&
 +      test_branch_upstream track . master
 +'
 +
 +# setup remote repository $1 and repository $2 with $1 set up as
 +# remote.  The remote has two branches, master and foo.
 +setup_remote_repo () {
 +      git init $1 &&
 +      (
 +              cd $1 &&
 +              test_commit $1_master &&
 +              git checkout -b foo &&
 +              test_commit upstream_foo
 +      ) &&
 +      git init $2 &&
 +      (
 +              cd $2 &&
 +              test_commit $2_master &&
 +              git remote add $1 ../$1 &&
 +              git config remote.$1.fetch \
 +                      "refs/heads/*:refs/remotes/$1/*" &&
 +              git fetch --all
 +      )
 +}
 +
 +test_expect_success '--no-track avoids setting up tracking' '
 +      test_when_finished rm -rf repo_upstream repo_local foo &&
 +      setup_remote_repo repo_upstream repo_local &&
 +      (
 +              cd repo_local &&
 +              git worktree add --no-track -b foo ../foo repo_upstream/foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_must_fail git config "branch.foo.remote" &&
 +              test_must_fail git config "branch.foo.merge" &&
 +              test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
 +      )
 +'
 +
 +test_expect_success '"add" <path> <non-existent-branch> fails' '
 +      test_must_fail git worktree add foo non-existent
 +'
 +
 +test_expect_success '"add" <path> <branch> dwims' '
 +      test_when_finished rm -rf repo_upstream repo_dwim foo &&
 +      setup_remote_repo repo_upstream repo_dwim &&
 +      git init repo_dwim &&
 +      (
 +              cd repo_dwim &&
 +              git worktree add ../foo foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_branch_upstream foo repo_upstream foo &&
 +              test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
 +      )
 +'
 +
 +test_expect_success 'git worktree add does not match remote' '
 +      test_when_finished rm -rf repo_a repo_b foo &&
 +      setup_remote_repo repo_a repo_b &&
 +      (
 +              cd repo_b &&
 +              git worktree add ../foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_must_fail git config "branch.foo.remote" &&
 +              test_must_fail git config "branch.foo.merge" &&
 +              ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 +      )
 +'
 +
 +test_expect_success 'git worktree add --guess-remote sets up tracking' '
 +      test_when_finished rm -rf repo_a repo_b foo &&
 +      setup_remote_repo repo_a repo_b &&
 +      (
 +              cd repo_b &&
 +              git worktree add --guess-remote ../foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_branch_upstream foo repo_a foo &&
 +              test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 +      )
 +'
 +
 +test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
 +      test_when_finished rm -rf repo_a repo_b foo &&
 +      setup_remote_repo repo_a repo_b &&
 +      (
 +              cd repo_b &&
 +              git config worktree.guessRemote true &&
 +              git worktree add ../foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_branch_upstream foo repo_a foo &&
 +              test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 +      )
 +'
 +
 +test_expect_success 'git worktree --no-guess-remote option overrides config' '
 +      test_when_finished rm -rf repo_a repo_b foo &&
 +      setup_remote_repo repo_a repo_b &&
 +      (
 +              cd repo_b &&
 +              git config worktree.guessRemote true &&
 +              git worktree add --no-guess-remote ../foo
 +      ) &&
 +      (
 +              cd foo &&
 +              test_must_fail git config "branch.foo.remote" &&
 +              test_must_fail git config "branch.foo.merge" &&
 +              ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
 +      )
 +'
 +
 +post_checkout_hook () {
 +      test_when_finished "rm -f .git/hooks/post-checkout" &&
 +      mkdir -p .git/hooks &&
 +      write_script .git/hooks/post-checkout <<-\EOF
 +      echo $* >hook.actual
 +      EOF
 +}
 +
 +test_expect_success '"add" invokes post-checkout hook (branch)' '
 +      post_checkout_hook &&
 +      printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
 +      git worktree add gumby &&
 +      test_cmp hook.expect hook.actual
 +'
 +
 +test_expect_success '"add" invokes post-checkout hook (detached)' '
 +      post_checkout_hook &&
 +      printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
 +      git worktree add --detach grumpy &&
 +      test_cmp hook.expect hook.actual
 +'
 +
 +test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
 +      post_checkout_hook &&
 +      rm -f hook.actual &&
 +      git worktree add --no-checkout gloopy &&
 +      test_path_is_missing hook.actual
 +'
 +
  test_done