Imported Upstream version 2.4.0
[platform/upstream/git.git] / builtin / clone.c
index 8d23a62..53a2e5a 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include "builtin.h"
+#include "lockfile.h"
 #include "parse-options.h"
 #include "fetch-pack.h"
 #include "refs.h"
 #include "transport.h"
 #include "strbuf.h"
 #include "dir.h"
-#include "pack-refs.h"
 #include "sigchain.h"
 #include "branch.h"
 #include "remote.h"
 #include "run-command.h"
+#include "connected.h"
 
 /*
  * Overall FIXMEs:
@@ -33,7 +34,7 @@
  *
  */
 static const char * const builtin_clone_usage[] = {
-       N_("git clone [options] [--] <repo> [<dir>]"),
+       N_("git clone [<options>] [--] <repo> [<dir>]"),
        NULL
 };
 
@@ -48,6 +49,7 @@ static int option_verbosity;
 static int option_progress = -1;
 static struct string_list option_config;
 static struct string_list option_reference;
+static int option_dissociate;
 
 static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
 {
@@ -62,23 +64,22 @@ static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
        OPT_BOOL(0, "progress", &option_progress,
                 N_("force progress reporting")),
-       OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
-                   N_("don't create a checkout")),
-       OPT_BOOLEAN(0, "bare", &option_bare, N_("create a bare repository")),
-       { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
-               N_("create a bare repository"),
-               PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
-       OPT_BOOLEAN(0, "mirror", &option_mirror,
-                   N_("create a mirror repository (implies bare)")),
+       OPT_BOOL('n', "no-checkout", &option_no_checkout,
+                N_("don't create a checkout")),
+       OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")),
+       OPT_HIDDEN_BOOL(0, "naked", &option_bare,
+                       N_("create a bare repository")),
+       OPT_BOOL(0, "mirror", &option_mirror,
+                N_("create a mirror repository (implies bare)")),
        OPT_BOOL('l', "local", &option_local,
                N_("to clone from a local repository")),
-       OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
+       OPT_BOOL(0, "no-hardlinks", &option_no_hardlinks,
                    N_("don't use local hardlinks, always copy")),
-       OPT_BOOLEAN('s', "shared", &option_shared,
+       OPT_BOOL('s', "shared", &option_shared,
                    N_("setup as shared repository")),
-       OPT_BOOLEAN(0, "recursive", &option_recursive,
+       OPT_BOOL(0, "recursive", &option_recursive,
                    N_("initialize submodules in the clone")),
-       OPT_BOOLEAN(0, "recurse-submodules", &option_recursive,
+       OPT_BOOL(0, "recurse-submodules", &option_recursive,
                    N_("initialize submodules in the clone")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
                   N_("directory from which templates will be used")),
@@ -94,6 +95,8 @@ static struct option builtin_clone_options[] = {
                    N_("create a shallow clone of that depth")),
        OPT_BOOL(0, "single-branch", &option_single_branch,
                    N_("clone only one branch, HEAD or --branch")),
+       OPT_BOOL(0, "dissociate", &option_dissociate,
+                N_("use --reference only while cloning")),
        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"),
@@ -231,18 +234,34 @@ static void strip_trailing_slashes(char *dir)
 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: real_path() and mkpath() return static buffer */
+       /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
        ref_git = xstrdup(real_path(item->string));
-       if (is_directory(mkpath("%s/.git/objects", ref_git))) {
+
+       repo = read_gitfile(ref_git);
+       if (!repo)
+               repo = read_gitfile(mkpath("%s/.git", ref_git));
+       if (repo) {
+               free(ref_git);
+               ref_git = xstrdup(repo);
+       }
+
+       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)))
-               die(_("reference repository '%s' is not a local directory."),
+               die(_("reference repository '%s' is not a local repository."),
                    item->string);
 
+       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);
@@ -370,18 +389,37 @@ static void clone_local(const char *src_repo, const char *dest_repo)
        }
 
        if (0 <= option_verbosity)
-               printf(_("done.\n"));
+               fprintf(stderr, _("done.\n"));
 }
 
 static const char *junk_work_tree;
 static const char *junk_git_dir;
-static pid_t junk_pid;
+static enum {
+       JUNK_LEAVE_NONE,
+       JUNK_LEAVE_REPO,
+       JUNK_LEAVE_ALL
+} junk_mode = JUNK_LEAVE_NONE;
+
+static const char junk_leave_repo_msg[] =
+N_("Clone succeeded, but checkout failed.\n"
+   "You can inspect what was checked out with 'git status'\n"
+   "and retry the checkout with 'git checkout -f HEAD'\n");
 
 static void remove_junk(void)
 {
        struct strbuf sb = STRBUF_INIT;
-       if (getpid() != junk_pid)
+
+       switch (junk_mode) {
+       case JUNK_LEAVE_REPO:
+               warning("%s", _(junk_leave_repo_msg));
+               /* fall-through */
+       case JUNK_LEAVE_ALL:
                return;
+       default:
+               /* proceed to removal */
+               break;
+       }
+
        if (junk_git_dir) {
                strbuf_addstr(&sb, junk_git_dir);
                remove_dir_recursively(&sb, 0);
@@ -461,36 +499,73 @@ static void write_remote_refs(const struct ref *local_refs)
 {
        const struct ref *r;
 
+       lock_packed_refs(LOCK_DIE_ON_ERROR);
+
        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);
+       if (commit_packed_refs())
+               die_errno("unable to overwrite old ref-pack file");
 }
 
 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/"))
+               if (!starts_with(ref->name, "refs/tags/"))
                        continue;
-               if (!suffixcmp(ref->name, "^{}"))
+               if (ends_with(ref->name, "^{}"))
                        continue;
                if (!has_sha1_file(ref->old_sha1))
                        continue;
                update_ref(msg, ref->name, ref->old_sha1,
-                          NULL, 0, DIE_ON_ERR);
+                          NULL, 0, UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
+static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+{
+       struct ref **rm = cb_data;
+       struct ref *ref = *rm;
+
+       /*
+        * Skip anything missing a peer_ref, which we are not
+        * actually going to write a ref for.
+        */
+       while (ref && !ref->peer_ref)
+               ref = ref->next;
+       /* Returning -1 notes "end of list" to the caller. */
+       if (!ref)
+               return -1;
+
+       hashcpy(sha1, ref->old_sha1);
+       *rm = ref->next;
+       return 0;
+}
+
 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)
+                              const char *msg,
+                              struct transport *transport,
+                              int check_connectivity)
 {
+       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))
+                       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)
@@ -510,19 +585,20 @@ static void update_remote_refs(const struct ref *refs,
 static void update_head(const struct ref *our, const struct ref *remote,
                        const char *msg)
 {
-       if (our && !prefixcmp(our->name, "refs/heads/")) {
+       const char *head;
+       if (our && skip_prefix(our->name, "refs/heads/", &head)) {
                /* 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);
+                       update_ref(msg, "HEAD", our->old_sha1, 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);
                /* --branch specifies a non-branch (i.e. tags), detach HEAD */
                update_ref(msg, "HEAD", c->object.sha1,
-                          NULL, REF_NODEREF, DIE_ON_ERR);
+                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
        } else if (remote) {
                /*
                 * We know remote HEAD points to a non-branch, or
@@ -530,7 +606,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
                 * Detach HEAD in all these cases.
                 */
                update_ref(msg, "HEAD", remote->old_sha1,
-                          NULL, REF_NODEREF, DIE_ON_ERR);
+                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
@@ -542,12 +618,12 @@ static int checkout(void)
        struct unpack_trees_options opts;
        struct tree *tree;
        struct tree_desc t;
-       int err = 0, fd;
+       int err = 0;
 
        if (option_no_checkout)
                return 0;
 
-       head = resolve_refdup("HEAD", sha1, 1, NULL);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
@@ -557,7 +633,7 @@ static int checkout(void)
                if (advice_detached_head)
                        detach_advice(sha1_to_hex(sha1));
        } else {
-               if (prefixcmp(head, "refs/heads/"))
+               if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        }
        free(head);
@@ -566,7 +642,7 @@ static int checkout(void)
        setup_work_tree();
 
        lock_file = xcalloc(1, sizeof(struct lock_file));
-       fd = hold_locked_index(lock_file, 1);
+       hold_locked_index(lock_file, 1);
 
        memset(&opts, 0, sizeof opts);
        opts.update = 1;
@@ -579,14 +655,14 @@ static int checkout(void)
        tree = parse_tree_indirect(sha1);
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
-       unpack_trees(1, &t, &opts);
+       if (unpack_trees(1, &t, &opts) < 0)
+               die(_("unable to checkout working tree"));
 
-       if (write_cache(fd, active_cache, active_nr) ||
-           commit_locked_index(lock_file))
+       if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
-       err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-                       sha1_to_hex(sha1), "1", NULL);
+       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);
@@ -610,9 +686,10 @@ static void write_config(struct string_list *config)
        }
 }
 
-static void write_refspec_config(const char* src_ref_prefix,
-               const struct ref* our_head_points_at,
-               const struct ref* remote_head_points_at, struct strbuf* branch_top)
+static void write_refspec_config(const char *src_ref_prefix,
+               const struct ref *our_head_points_at,
+               const struct ref *remote_head_points_at,
+               struct strbuf *branch_top)
 {
        struct strbuf key = STRBUF_INIT;
        struct strbuf value = STRBUF_INIT;
@@ -620,21 +697,24 @@ static void write_refspec_config(const char* src_ref_prefix,
        if (option_mirror || !option_bare) {
                if (option_single_branch && !option_mirror) {
                        if (option_branch) {
-                               if (strstr(our_head_points_at->name, "refs/tags/"))
+                               if (starts_with(our_head_points_at->name, "refs/tags/"))
                                        strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
                                                our_head_points_at->name);
                                else
                                        strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
                                                branch_top->buf, option_branch);
                        } else if (remote_head_points_at) {
+                               const char *head = remote_head_points_at->name;
+                               if (!skip_prefix(head, "refs/heads/", &head))
+                                       die("BUG: remote HEAD points at non-head?");
+
                                strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
-                                               branch_top->buf,
-                                               skip_prefix(remote_head_points_at->name, "refs/heads/"));
+                                               branch_top->buf, head);
                        }
                        /*
                         * otherwise, the next "git fetch" will
                         * simply fetch from HEAD without updating
-                        * any remote tracking branch, which is what
+                        * any remote-tracking branch, which is what
                         * we want.
                         */
                } else {
@@ -658,6 +738,16 @@ static void write_refspec_config(const char* src_ref_prefix,
        strbuf_release(&value);
 }
 
+static void dissociate_from_references(void)
+{
+       static const char* argv[] = { "repack", "-a", "-d", NULL };
+
+       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"));
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
        int is_bundle = 0, is_local;
@@ -680,8 +770,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        struct refspec *refspec;
        const char *fetch_pattern;
 
-       junk_pid = getpid();
-
        packet_trace_identity("clone");
        argc = parse_options(argc, argv, prefix, builtin_clone_options,
                             builtin_clone_usage, 0);
@@ -704,6 +792,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_origin)
                        die(_("--bare and --origin %s options are incompatible."),
                            option_origin);
+               if (real_git_dir)
+                       die(_("--bare and --separate-git-dir are incompatible."));
                option_no_checkout = 1;
        }
 
@@ -719,9 +809,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                die(_("repository '%s' does not exist"), repo_name);
        else
                repo = repo_name;
-       is_local = option_local != 0 && path && !is_bundle;
-       if (is_local && option_depth)
-               warning(_("--depth is ignored in local clones; use file:// instead."));
+
+       /* no need to be strict, transport_set_option() will validate it again */
+       if (option_depth && atoi(option_depth) < 1)
+               die(_("depth %s is not a positive number"), option_depth);
 
        if (argc == 2)
                dir = xstrdup(argv[1]);
@@ -751,22 +842,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                git_dir = mkpathdup("%s/.git", dir);
        }
 
+       atexit(remove_junk);
+       sigchain_push_common(remove_junk_on_signal);
+
        if (!option_bare) {
-               junk_work_tree = work_tree;
                if (safe_create_leading_directories_const(work_tree) < 0)
                        die_errno(_("could not create leading directories of '%s'"),
                                  work_tree);
                if (!dest_exists && mkdir(work_tree, 0777))
-                       die_errno(_("could not create work tree dir '%s'."),
+                       die_errno(_("could not create work tree dir '%s'"),
                                  work_tree);
+               junk_work_tree = work_tree;
                set_git_work_tree(work_tree);
        }
-       junk_git_dir = git_dir;
-       atexit(remove_junk);
-       sigchain_push_common(remove_junk_on_signal);
-
-       setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
 
+       junk_git_dir = git_dir;
        if (safe_create_leading_directories_const(git_dir) < 0)
                die(_("could not create leading directories of '%s'"), git_dir);
 
@@ -778,20 +868,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (0 <= option_verbosity) {
                if (option_bare)
-                       printf(_("Cloning into bare repository '%s'...\n"), dir);
+                       fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
                else
-                       printf(_("Cloning into '%s'...\n"), dir);
+                       fprintf(stderr, _("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
-        * environment variable.  We actually need to unset it, too, to
-        * re-enable parsing of the global configs.
-        */
-       unsetenv(CONFIG_ENVIRONMENT);
-
        git_config(git_default_config, NULL);
 
        if (option_bare) {
@@ -811,6 +894,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (option_reference.nr)
                setup_reference();
+       else if (option_dissociate) {
+               warning(_("--dissociate given, but there is no --reference"));
+               option_dissociate = 0;
+       }
 
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
@@ -819,25 +906,40 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        remote = remote_get(option_origin);
        transport = transport_get(remote, remote->url[0]);
+       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 (!access(mkpath("%s/shallow", path), F_OK)) {
+                       if (option_local > 0)
+                               warning(_("source repository is shallow, ignoring --local"));
+                       is_local = 0;
+               }
+       }
+       if (option_local > 0 && !is_local)
+               warning(_("--local is ignored"));
+       transport->cloning = 1;
 
-       if (!is_local) {
-               if (!transport->get_refs_list || !transport->fetch)
-                       die(_("Don't know how to clone %s"), transport->url);
+       if (!transport->get_refs_list || (!is_local && !transport->fetch))
+               die(_("Don't know how to clone %s"), transport->url);
 
-               transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+       transport_set_option(transport, TRANS_OPT_KEEP, "yes");
 
-               if (option_depth)
-                       transport_set_option(transport, TRANS_OPT_DEPTH,
-                                            option_depth);
-               if (option_single_branch)
-                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+       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);
+       transport_set_verbosity(transport, option_verbosity, option_progress);
 
-               if (option_upload_pack)
-                       transport_set_option(transport, TRANS_OPT_UPLOADPACK,
-                                            option_upload_pack);
-       }
+       if (option_upload_pack)
+               transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+                                    option_upload_pack);
+
+       if (transport->smart_options && !option_depth)
+               transport->smart_options->check_self_contained_and_connected = 1;
 
        refs = transport_get_remote_refs(transport);
 
@@ -878,6 +980,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        our_head_points_at = remote_head_points_at;
        }
        else {
+               if (option_branch)
+                       die(_("Remote branch %s not found in upstream %s"),
+                                       option_branch, option_origin);
+
                warning(_("You appear to have cloned an empty repository."));
                mapped_refs = NULL;
                our_head_points_at = NULL;
@@ -898,19 +1004,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                transport_fetch_refs(transport, mapped_refs);
 
        update_remote_refs(refs, mapped_refs, remote_head_points_at,
-                          branch_top.buf, reflog_msg.buf);
+                          branch_top.buf, reflog_msg.buf, transport, !is_local);
 
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
        transport_unlock_pack(transport);
        transport_disconnect(transport);
 
+       if (option_dissociate)
+               dissociate_from_references();
+
+       junk_mode = JUNK_LEAVE_REPO;
        err = checkout();
 
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
        strbuf_release(&key);
        strbuf_release(&value);
-       junk_pid = 0;
+       junk_mode = JUNK_LEAVE_ALL;
+
+       free(refspec);
        return err;
 }