builtin-blame: Reencode commit messages according to git-log rules.
[gitweb.git] / builtin-clone.c
index b2dfe1ab5c6df0da0af0004bdf5accd6d37a90d8..8e1a1d399580af3faa39c3c50350b3ca19df0634 100644 (file)
@@ -18,6 +18,7 @@
 #include "transport.h"
 #include "strbuf.h"
 #include "dir.h"
+#include "pack-refs.h"
 
 /*
  * Overall FIXMEs:
  *
  */
 static const char * const builtin_clone_usage[] = {
-       "git-clone [options] [--] <repo> [<dir>]",
+       "git clone [options] [--] <repo> [<dir>]",
        NULL
 };
 
-static int option_quiet, option_no_checkout, option_bare;
+static int option_quiet, option_no_checkout, option_bare, option_mirror;
 static int option_local, option_no_hardlinks, option_shared;
 static char *option_template, *option_reference, *option_depth;
 static char *option_origin = NULL;
 static char *option_upload_pack = "git-upload-pack";
+static int option_verbose;
 
 static struct option builtin_clone_options[] = {
        OPT__QUIET(&option_quiet),
+       OPT__VERBOSE(&option_verbose),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
        OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
+       OPT_BOOLEAN(0, "mirror", &option_mirror,
+                   "create a mirror repository (implies bare)"),
        OPT_BOOLEAN('l', "local", &option_local,
                    "to clone from a local repository"),
        OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
@@ -55,7 +60,7 @@ static struct option builtin_clone_options[] = {
        OPT_STRING(0, "reference", &option_reference, "repo",
                   "reference repository"),
        OPT_STRING('o', "origin", &option_origin, "branch",
-                  "use <branch> instead or 'origin' to track upstream"),
+                  "use <branch> instead of 'origin' to track upstream"),
        OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
@@ -74,7 +79,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
        for (i = 0; i < ARRAY_SIZE(suffix); i++) {
                const char *path;
                path = mkpath("%s%s", repo, suffix[i]);
-               if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
+               if (is_directory(path)) {
                        *is_bundle = 0;
                        return xstrdup(make_nonrelative_path(path));
                }
@@ -92,47 +97,58 @@ static char *get_repo_path(const char *repo, int *is_bundle)
        return NULL;
 }
 
-static char *guess_dir_name(const char *repo, int is_bundle)
+static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
 {
-       const char *p, *start, *end, *limit;
-       int after_slash_or_colon;
-
-       /* Guess dir name from repository: strip trailing '/',
-        * strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */
-
-       after_slash_or_colon = 1;
-       limit = repo + strlen(repo);
-       start = repo;
-       end = limit;
-       for (p = repo; p < limit; p++) {
-               const char *prefix = is_bundle ? ".bundle" : ".git";
-               if (!prefixcmp(p, prefix)) {
-                       if (!after_slash_or_colon)
-                               end = p;
-                       p += strlen(prefix) - 1;
-               } else if (!prefixcmp(p, ".bundle")) {
-                       if (!after_slash_or_colon)
-                               end = p;
-                       p += 7;
-               } else if (*p == '/' || *p == ':') {
-                       if (end == limit)
-                               end = p;
-                       after_slash_or_colon = 1;
-               } else if (after_slash_or_colon) {
-                       start = p;
-                       end = limit;
-                       after_slash_or_colon = 0;
-               }
+       const char *end = repo + strlen(repo), *start;
+
+       /*
+        * Strip trailing slashes and /.git
+        */
+       while (repo < end && is_dir_sep(end[-1]))
+               end--;
+       if (end - repo > 5 && is_dir_sep(end[-5]) &&
+           !strncmp(end - 4, ".git", 4)) {
+               end -= 5;
+               while (repo < end && is_dir_sep(end[-1]))
+                       end--;
+       }
+
+       /*
+        * Find last component, but be prepared that repo could have
+        * the form  "remote.example.com:foo.git", i.e. no slash
+        * in the directory part.
+        */
+       start = end;
+       while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
+               start--;
+
+       /*
+        * Strip .{bundle,git}.
+        */
+       if (is_bundle) {
+               if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
+                       end -= 7;
+       } else {
+               if (end - start > 4 && !strncmp(end - 4, ".git", 4))
+                       end -= 4;
+       }
+
+       if (is_bare) {
+               char *result = xmalloc(end - start + 5);
+               sprintf(result, "%.*s.git", (int)(end - start), start);
+               return result;
        }
 
        return xstrndup(start, end - start);
 }
 
-static int is_directory(const char *path)
+static void strip_trailing_slashes(char *dir)
 {
-       struct stat buf;
+       char *end = dir + strlen(dir);
 
-       return !stat(path, &buf) && S_ISDIR(buf.st_mode);
+       while (dir < end - 1 && is_dir_sep(end[-1]))
+               end--;
+       *end = '\0';
 }
 
 static void setup_reference(const char *repo)
@@ -250,10 +266,9 @@ pid_t junk_pid;
 
 static void remove_junk(void)
 {
-       struct strbuf sb;
+       struct strbuf sb = STRBUF_INIT;
        if (getpid() != junk_pid)
                return;
-       strbuf_init(&sb, 0);
        if (junk_git_dir) {
                strbuf_addstr(&sb, junk_git_dir);
                remove_dir_recursively(&sb, 0);
@@ -318,11 +333,15 @@ static struct ref *write_remote_refs(const struct ref *refs,
        struct ref *r;
 
        get_fetch_map(refs, refspec, &tail, 0);
-       get_fetch_map(refs, tag_refspec, &tail, 0);
+       if (!option_mirror)
+               get_fetch_map(refs, tag_refspec, &tail, 0);
 
        for (r = local_refs; r; r = r->next)
-               update_ref(reflog,
-                          r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR);
+               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+
+       pack_refs(PACK_REFS_ALL);
+       clear_extra_refs();
+
        return local_refs;
 }
 
@@ -336,7 +355,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        char *path, *dir;
        const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
        char branch_top[256], key[256], value[256];
-       struct strbuf reflog_msg;
+       struct strbuf reflog_msg = STRBUF_INIT;
+       struct transport *transport = NULL;
+       char *src_ref_prefix = "refs/heads/";
 
        struct refspec refspec;
 
@@ -351,6 +372,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (option_no_hardlinks)
                use_local_hardlinks = 0;
 
+       if (option_mirror)
+               option_bare = 1;
+
        if (option_bare) {
                if (option_origin)
                        die("--bare and --origin %s options are incompatible.",
@@ -366,7 +390,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        path = get_repo_path(repo_name, &is_bundle);
        if (path)
-               repo = path;
+               repo = xstrdup(make_nonrelative_path(repo_name));
        else if (!strchr(repo_name, ':'))
                repo = xstrdup(make_absolute_path(repo_name));
        else
@@ -375,12 +399,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (argc == 2)
                dir = xstrdup(argv[1]);
        else
-               dir = guess_dir_name(repo_name, is_bundle);
+               dir = guess_dir_name(repo_name, is_bundle, option_bare);
+       strip_trailing_slashes(dir);
 
        if (!stat(dir, &buf))
                die("destination directory '%s' already exists.", dir);
 
-       strbuf_init(&reflog_msg, 0);
        strbuf_addf(&reflog_msg, "clone: from %s", repo);
 
        if (option_bare)
@@ -401,10 +425,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (!option_bare) {
                junk_work_tree = work_tree;
                if (safe_create_leading_directories_const(work_tree) < 0)
-                       die("could not create leading directories of '%s'",
-                                       work_tree);
+                       die("could not create leading directories of '%s': %s",
+                                       work_tree, strerror(errno));
                if (mkdir(work_tree, 0755))
-                       die("could not create work tree dir '%s'.", work_tree);
+                       die("could not create work tree dir '%s': %s.",
+                                       work_tree, strerror(errno));
                set_git_work_tree(work_tree);
        }
        junk_git_dir = git_dir;
@@ -417,42 +442,58 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                die("could not create leading directories of '%s'", git_dir);
        set_git_dir(make_absolute_path(git_dir));
 
-       fprintf(stderr, "Initialize %s\n", git_dir);
        init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
 
+       /*
+        * 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);
+
        if (option_reference)
                setup_reference(git_dir);
 
        git_config(git_default_config, NULL);
 
        if (option_bare) {
-               strcpy(branch_top, "refs/heads/");
+               if (option_mirror)
+                       src_ref_prefix = "refs/";
+               strcpy(branch_top, src_ref_prefix);
 
                git_config_set("core.bare", "true");
        } else {
                snprintf(branch_top, sizeof(branch_top),
                         "refs/remotes/%s/", option_origin);
+       }
 
+       if (option_mirror || !option_bare) {
                /* Configure the remote */
+               if (option_mirror) {
+                       snprintf(key, sizeof(key),
+                                       "remote.%s.mirror", option_origin);
+                       git_config_set(key, "true");
+               }
+
                snprintf(key, sizeof(key), "remote.%s.url", option_origin);
                git_config_set(key, repo);
 
                snprintf(key, sizeof(key), "remote.%s.fetch", option_origin);
                snprintf(value, sizeof(value),
-                               "+refs/heads/*:%s*", branch_top);
+                               "+%s*:%s*", src_ref_prefix, branch_top);
                git_config_set_multivar(key, value, "^$", 0);
        }
 
        refspec.force = 0;
        refspec.pattern = 1;
-       refspec.src = "refs/heads/";
+       refspec.src = src_ref_prefix;
        refspec.dst = branch_top;
 
        if (path && !is_bundle)
                refs = clone_local(path, git_dir);
        else {
                struct remote *remote = remote_get(argv[0]);
-               struct transport *transport = transport_get(remote, argv[0]);
+               transport = transport_get(remote, remote->url[0]);
 
                if (!transport->get_refs_list || !transport->fetch)
                        die("Don't know how to clone %s", transport->url);
@@ -465,6 +506,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
                if (option_quiet)
                        transport->verbose = -1;
+               else if (option_verbose)
+                       transport->progress = 1;
+
+               if (option_upload_pack)
+                       transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+                                            option_upload_pack);
 
                refs = transport_get_remote_refs(transport);
                transport_fetch_refs(transport, refs);
@@ -481,7 +528,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                create_symref("HEAD", head_points_at->name, NULL);
 
                if (!option_bare) {
-                       struct strbuf head_ref;
+                       struct strbuf head_ref = STRBUF_INIT;
                        const char *head = head_points_at->name;
 
                        if (!prefixcmp(head, "refs/heads/"))
@@ -494,7 +541,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                   head_points_at->old_sha1,
                                   NULL, 0, DIE_ON_ERR);
 
-                       strbuf_init(&head_ref, 0);
                        strbuf_addstr(&head_ref, branch_top);
                        strbuf_addstr(&head_ref, "HEAD");
 
@@ -522,6 +568,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                option_no_checkout = 1;
        }
 
+       if (transport)
+               transport_unlock_pack(transport);
+
        if (!option_no_checkout) {
                struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
                struct unpack_trees_options opts;