clone: use a real progress meter for connectivity check
[gitweb.git] / builtin / submodule--helper.c
index 3bd6883eff842ee139a3d24475401d75f65fe53e..b22352b6e1e4c40d1a6e182f5c12abf6351f71fb 100644 (file)
@@ -9,6 +9,211 @@
 #include "submodule-config.h"
 #include "string-list.h"
 #include "run-command.h"
+#include "remote.h"
+#include "refs.h"
+#include "connect.h"
+
+static char *get_default_remote(void)
+{
+       char *dest = NULL, *ret;
+       unsigned char sha1[20];
+       struct strbuf sb = STRBUF_INIT;
+       const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
+
+       if (!refname)
+               die(_("No such ref: %s"), "HEAD");
+
+       /* detached HEAD */
+       if (!strcmp(refname, "HEAD"))
+               return xstrdup("origin");
+
+       if (!skip_prefix(refname, "refs/heads/", &refname))
+               die(_("Expecting a full ref name, got %s"), refname);
+
+       strbuf_addf(&sb, "branch.%s.remote", refname);
+       if (git_config_get_string(sb.buf, &dest))
+               ret = xstrdup("origin");
+       else
+               ret = dest;
+
+       strbuf_release(&sb);
+       return ret;
+}
+
+static int starts_with_dot_slash(const char *str)
+{
+       return str[0] == '.' && is_dir_sep(str[1]);
+}
+
+static int starts_with_dot_dot_slash(const char *str)
+{
+       return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
+}
+
+/*
+ * Returns 1 if it was the last chop before ':'.
+ */
+static int chop_last_dir(char **remoteurl, int is_relative)
+{
+       char *rfind = find_last_dir_sep(*remoteurl);
+       if (rfind) {
+               *rfind = '\0';
+               return 0;
+       }
+
+       rfind = strrchr(*remoteurl, ':');
+       if (rfind) {
+               *rfind = '\0';
+               return 1;
+       }
+
+       if (is_relative || !strcmp(".", *remoteurl))
+               die(_("cannot strip one component off url '%s'"),
+                       *remoteurl);
+
+       free(*remoteurl);
+       *remoteurl = xstrdup(".");
+       return 0;
+}
+
+/*
+ * The `url` argument is the URL that navigates to the submodule origin
+ * repo. When relative, this URL is relative to the superproject origin
+ * URL repo. The `up_path` argument, if specified, is the relative
+ * path that navigates from the submodule working tree to the superproject
+ * working tree. Returns the origin URL of the submodule.
+ *
+ * Return either an absolute URL or filesystem path (if the superproject
+ * origin URL is an absolute URL or filesystem path, respectively) or a
+ * relative file system path (if the superproject origin URL is a relative
+ * file system path).
+ *
+ * When the output is a relative file system path, the path is either
+ * relative to the submodule working tree, if up_path is specified, or to
+ * the superproject working tree otherwise.
+ *
+ * NEEDSWORK: This works incorrectly on the domain and protocol part.
+ * remote_url      url              outcome          expectation
+ * http://a.com/b  ../c             http://a.com/c   as is
+ * http://a.com/b  ../../c          http://c         error out
+ * http://a.com/b  ../../../c       http:/c          error out
+ * http://a.com/b  ../../../../c    http:c           error out
+ * http://a.com/b  ../../../../../c    .:c           error out
+ * NEEDSWORK: Given how chop_last_dir() works, this function is broken
+ * when a local part has a colon in its path component, too.
+ */
+static char *relative_url(const char *remote_url,
+                               const char *url,
+                               const char *up_path)
+{
+       int is_relative = 0;
+       int colonsep = 0;
+       char *out;
+       char *remoteurl = xstrdup(remote_url);
+       struct strbuf sb = STRBUF_INIT;
+       size_t len = strlen(remoteurl);
+
+       if (is_dir_sep(remoteurl[len]))
+               remoteurl[len] = '\0';
+
+       if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
+               is_relative = 0;
+       else {
+               is_relative = 1;
+               /*
+                * Prepend a './' to ensure all relative
+                * remoteurls start with './' or '../'
+                */
+               if (!starts_with_dot_slash(remoteurl) &&
+                   !starts_with_dot_dot_slash(remoteurl)) {
+                       strbuf_reset(&sb);
+                       strbuf_addf(&sb, "./%s", remoteurl);
+                       free(remoteurl);
+                       remoteurl = strbuf_detach(&sb, NULL);
+               }
+       }
+       /*
+        * When the url starts with '../', remove that and the
+        * last directory in remoteurl.
+        */
+       while (url) {
+               if (starts_with_dot_dot_slash(url)) {
+                       url += 3;
+                       colonsep |= chop_last_dir(&remoteurl, is_relative);
+               } else if (starts_with_dot_slash(url))
+                       url += 2;
+               else
+                       break;
+       }
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
+       free(remoteurl);
+
+       if (starts_with_dot_slash(sb.buf))
+               out = xstrdup(sb.buf + 2);
+       else
+               out = xstrdup(sb.buf);
+       strbuf_reset(&sb);
+
+       if (!up_path || !is_relative)
+               return out;
+
+       strbuf_addf(&sb, "%s%s", up_path, out);
+       free(out);
+       return strbuf_detach(&sb, NULL);
+}
+
+static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+{
+       char *remoteurl = NULL;
+       char *remote = get_default_remote();
+       const char *up_path = NULL;
+       char *res;
+       const char *url;
+       struct strbuf sb = STRBUF_INIT;
+
+       if (argc != 2 && argc != 3)
+               die("resolve-relative-url only accepts one or two arguments");
+
+       url = argv[1];
+       strbuf_addf(&sb, "remote.%s.url", remote);
+       free(remote);
+
+       if (git_config_get_string(sb.buf, &remoteurl))
+               /* the repository is its own authoritative upstream */
+               remoteurl = xgetcwd();
+
+       if (argc == 3)
+               up_path = argv[2];
+
+       res = relative_url(remoteurl, url, up_path);
+       puts(res);
+       free(res);
+       free(remoteurl);
+       return 0;
+}
+
+static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
+{
+       char *remoteurl, *res;
+       const char *up_path, *url;
+
+       if (argc != 4)
+               die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>");
+
+       up_path = argv[1];
+       remoteurl = xstrdup(argv[2]);
+       url = argv[3];
+
+       if (!strcmp(up_path, "(null)"))
+               up_path = NULL;
+
+       res = relative_url(remoteurl, url, up_path);
+       puts(res);
+       free(res);
+       free(remoteurl);
+       return 0;
+}
 
 struct module_list {
        const struct cache_entry **entries;
@@ -82,10 +287,8 @@ static int module_list(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, module_list_options,
                             git_submodule_helper_usage, 0);
 
-       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) {
-               printf("#unmatched\n");
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
                return 1;
-       }
 
        for (i = 0; i < list.nr; i++) {
                const struct cache_entry *ce = list.entries[i];
@@ -100,71 +303,142 @@ static int module_list(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static int module_name(int argc, const char **argv, const char *prefix)
+static void init_submodule(const char *path, const char *prefix, int quiet)
 {
        const struct submodule *sub;
+       struct strbuf sb = STRBUF_INIT;
+       char *upd = NULL, *url = NULL, *displaypath;
 
-       if (argc != 2)
-               usage(_("git submodule--helper name <path>"));
-
+       /* Only loads from .gitmodules, no overlay with .git/config */
        gitmodules_config();
-       sub = submodule_from_path(null_sha1, argv[1]);
+
+       if (prefix) {
+               strbuf_addf(&sb, "%s%s", prefix, path);
+               displaypath = strbuf_detach(&sb, NULL);
+       } else
+               displaypath = xstrdup(path);
+
+       sub = submodule_from_path(null_sha1, path);
 
        if (!sub)
-               die(_("no submodule mapping found in .gitmodules for path '%s'"),
-                   argv[1]);
+               die(_("No url found for submodule path '%s' in .gitmodules"),
+                       displaypath);
 
-       printf("%s\n", sub->name);
+       /*
+        * Copy url setting when it is not set yet.
+        * To look up the url in .git/config, we must not fall back to
+        * .gitmodules, so look it up directly.
+        */
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "submodule.%s.url", sub->name);
+       if (git_config_get_string(sb.buf, &url)) {
+               url = xstrdup(sub->url);
 
-       return 0;
+               if (!url)
+                       die(_("No url found for submodule path '%s' in .gitmodules"),
+                               displaypath);
+
+               /* Possibly a url relative to parent */
+               if (starts_with_dot_dot_slash(url) ||
+                   starts_with_dot_slash(url)) {
+                       char *remoteurl, *relurl;
+                       char *remote = get_default_remote();
+                       struct strbuf remotesb = STRBUF_INIT;
+                       strbuf_addf(&remotesb, "remote.%s.url", remote);
+                       free(remote);
+
+                       if (git_config_get_string(remotesb.buf, &remoteurl))
+                               /*
+                                * The repository is its own
+                                * authoritative upstream
+                                */
+                               remoteurl = xgetcwd();
+                       relurl = relative_url(remoteurl, url, NULL);
+                       strbuf_release(&remotesb);
+                       free(remoteurl);
+                       free(url);
+                       url = relurl;
+               }
+
+               if (git_config_set_gently(sb.buf, url))
+                       die(_("Failed to register url for submodule path '%s'"),
+                           displaypath);
+               if (!quiet)
+                       fprintf(stderr,
+                               _("Submodule '%s' (%s) registered for path '%s'\n"),
+                               sub->name, url, displaypath);
+       }
+
+       /* Copy "update" setting when it is not set yet */
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "submodule.%s.update", sub->name);
+       if (git_config_get_string(sb.buf, &upd) &&
+           sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+               if (sub->update_strategy.type == SM_UPDATE_COMMAND) {
+                       fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"),
+                               sub->name);
+                       upd = xstrdup("none");
+               } else
+                       upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy));
+
+               if (git_config_set_gently(sb.buf, upd))
+                       die(_("Failed to register update mode for submodule path '%s'"), displaypath);
+       }
+       strbuf_release(&sb);
+       free(displaypath);
+       free(url);
+       free(upd);
 }
 
-/*
- * Rules to sanitize configuration variables that are Ok to be passed into
- * submodule operations from the parent project using "-c". Should only
- * include keys which are both (a) safe and (b) necessary for proper
- * operation.
- */
-static int submodule_config_ok(const char *var)
+static int module_init(int argc, const char **argv, const char *prefix)
 {
-       if (starts_with(var, "credential."))
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       int quiet = 0;
+       int i;
+
+       struct option module_init_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper init [<path>]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_init_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
                return 1;
+
+       for (i = 0; i < list.nr; i++)
+               init_submodule(list.entries[i]->name, prefix, quiet);
+
        return 0;
 }
 
-static int sanitize_submodule_config(const char *var, const char *value, void *data)
+static int module_name(int argc, const char **argv, const char *prefix)
 {
-       struct strbuf *out = data;
+       const struct submodule *sub;
 
-       if (submodule_config_ok(var)) {
-               if (out->len)
-                       strbuf_addch(out, ' ');
+       if (argc != 2)
+               usage(_("git submodule--helper name <path>"));
 
-               if (value)
-                       sq_quotef(out, "%s=%s", var, value);
-               else
-                       sq_quote_buf(out, var);
-       }
+       gitmodules_config();
+       sub = submodule_from_path(null_sha1, argv[1]);
 
-       return 0;
-}
+       if (!sub)
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                   argv[1]);
 
-static void prepare_submodule_repo_env(struct argv_array *out)
-{
-       const char * const *var;
-
-       for (var = local_repo_env; *var; var++) {
-               if (!strcmp(*var, CONFIG_DATA_ENVIRONMENT)) {
-                       struct strbuf sanitized_config = STRBUF_INIT;
-                       git_config_from_parameters(sanitize_submodule_config,
-                                                  &sanitized_config);
-                       argv_array_pushf(out, "%s=%s", *var, sanitized_config.buf);
-                       strbuf_release(&sanitized_config);
-               } else {
-                       argv_array_push(out, *var);
-               }
-       }
+       printf("%s\n", sub->name);
 
+       return 0;
 }
 
 static int clone_submodule(const char *path, const char *gitdir, const char *url,
@@ -294,22 +568,6 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static int module_sanitize_config(int argc, const char **argv, const char *prefix)
-{
-       struct strbuf sanitized_config = STRBUF_INIT;
-
-       if (argc > 1)
-               usage(_("git submodule--helper sanitize-config"));
-
-       git_config_from_parameters(sanitize_submodule_config, &sanitized_config);
-       if (sanitized_config.len)
-               printf("%s\n", sanitized_config.buf);
-
-       strbuf_release(&sanitized_config);
-
-       return 0;
-}
-
 struct submodule_update_clone {
        /* index into 'list', the list of submodules to look into for cloning */
        int current;
@@ -321,6 +579,7 @@ struct submodule_update_clone {
 
        /* configuration parameters which are passed on to the children */
        int quiet;
+       int recommend_shallow;
        const char *reference;
        const char *depth;
        const char *recursive_prefix;
@@ -331,10 +590,33 @@ struct submodule_update_clone {
 
        /* If we want to stop as fast as possible and return an error */
        unsigned quickstop : 1;
+
+       /* failed clones to be retried again */
+       const struct cache_entry **failed_clones;
+       int failed_clones_nr, failed_clones_alloc;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \
-       STRING_LIST_INIT_DUP, 0}
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \
+       STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
+
+
+static void next_submodule_warn_missing(struct submodule_update_clone *suc,
+               struct strbuf *out, const char *displaypath)
+{
+       /*
+        * Only mention uninitialized submodules when their
+        * paths have been specified.
+        */
+       if (suc->warn_if_uninitialized) {
+               strbuf_addf(out,
+                       _("Submodule path '%s' not initialized"),
+                       displaypath);
+               strbuf_addch(out, '\n');
+               strbuf_addstr(out,
+                       _("Maybe you want to use 'update --init'?"));
+               strbuf_addch(out, '\n');
+       }
+}
 
 /**
  * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to
@@ -370,6 +652,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        else
                displaypath = ce->name;
 
+       if (!sub) {
+               next_submodule_warn_missing(suc, out, displaypath);
+               goto cleanup;
+       }
+
        if (suc->update.type == SM_UPDATE_NONE
            || (suc->update.type == SM_UPDATE_UNSPECIFIED
                && sub->update_strategy.type == SM_UPDATE_NONE)) {
@@ -387,19 +674,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        strbuf_addf(&sb, "submodule.%s.url", sub->name);
        git_config_get_string(sb.buf, &url);
        if (!url) {
-               /*
-                * Only mention uninitialized submodules when their
-                * path have been specified
-                */
-               if (suc->warn_if_uninitialized) {
-                       strbuf_addf(out,
-                               _("Submodule path '%s' not initialized"),
-                               displaypath);
-                       strbuf_addch(out, '\n');
-                       strbuf_addstr(out,
-                               _("Maybe you want to use 'update --init'?"));
-                       strbuf_addch(out, '\n');
-               }
+               next_submodule_warn_missing(suc, out, displaypath);
                goto cleanup;
        }
 
@@ -426,6 +701,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                argv_array_push(&child->args, "--quiet");
        if (suc->prefix)
                argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
+       if (suc->recommend_shallow && sub->recommend_shallow == 1)
+               argv_array_push(&child->args, "--depth=1");
        argv_array_pushl(&child->args, "--path", sub->path, NULL);
        argv_array_pushl(&child->args, "--name", sub->name, NULL);
        argv_array_pushl(&child->args, "--url", url, NULL);
@@ -445,23 +722,47 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 static int update_clone_get_next_task(struct child_process *child,
                                      struct strbuf *err,
                                      void *suc_cb,
-                                     void **void_task_cb)
+                                     void **idx_task_cb)
 {
        struct submodule_update_clone *suc = suc_cb;
+       const struct cache_entry *ce;
+       int index;
 
        for (; suc->current < suc->list.nr; suc->current++) {
-               const struct cache_entry *ce = suc->list.entries[suc->current];
+               ce = suc->list.entries[suc->current];
                if (prepare_to_clone_next_submodule(ce, child, suc, err)) {
+                       int *p = xmalloc(sizeof(*p));
+                       *p = suc->current;
+                       *idx_task_cb = p;
                        suc->current++;
                        return 1;
                }
        }
+
+       /*
+        * The loop above tried cloning each submodule once, now try the
+        * stragglers again, which we can imagine as an extension of the
+        * entry list.
+        */
+       index = suc->current - suc->list.nr;
+       if (index < suc->failed_clones_nr) {
+               int *p;
+               ce = suc->failed_clones[index];
+               if (!prepare_to_clone_next_submodule(ce, child, suc, err))
+                       die("BUG: ce was a submodule before?");
+               p = xmalloc(sizeof(*p));
+               *p = suc->current;
+               *idx_task_cb = p;
+               suc->current ++;
+               return 1;
+       }
+
        return 0;
 }
 
 static int update_clone_start_failure(struct strbuf *err,
                                      void *suc_cb,
-                                     void *void_task_cb)
+                                     void *idx_task_cb)
 {
        struct submodule_update_clone *suc = suc_cb;
        suc->quickstop = 1;
@@ -471,15 +772,39 @@ static int update_clone_start_failure(struct strbuf *err,
 static int update_clone_task_finished(int result,
                                      struct strbuf *err,
                                      void *suc_cb,
-                                     void *void_task_cb)
+                                     void *idx_task_cb)
 {
+       const struct cache_entry *ce;
        struct submodule_update_clone *suc = suc_cb;
 
+       int *idxP = *(int**)idx_task_cb;
+       int idx = *idxP;
+       free(idxP);
+
        if (!result)
                return 0;
 
-       suc->quickstop = 1;
-       return 1;
+       if (idx < suc->list.nr) {
+               ce  = suc->list.entries[idx];
+               strbuf_addf(err, _("Failed to clone '%s'. Retry scheduled"),
+                           ce->name);
+               strbuf_addch(err, '\n');
+               ALLOC_GROW(suc->failed_clones,
+                          suc->failed_clones_nr + 1,
+                          suc->failed_clones_alloc);
+               suc->failed_clones[suc->failed_clones_nr++] = ce;
+               return 0;
+       } else {
+               idx = suc->current - suc->list.nr;
+               ce  = suc->failed_clones[idx];
+               strbuf_addf(err, _("Failed to clone '%s' a second time, aborting"),
+                           ce->name);
+               strbuf_addch(err, '\n');
+               suc->quickstop = 1;
+               return 1;
+       }
+
+       return 0;
 }
 
 static int update_clone(int argc, const char **argv, const char *prefix)
@@ -508,6 +833,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
                              "specified number of revisions")),
                OPT_INTEGER('j', "jobs", &max_jobs,
                            N_("parallel jobs")),
+               OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+                           N_("whether the initial clone should follow the shallow recommendation")),
                OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
                OPT_END()
        };
@@ -561,6 +888,17 @@ static int update_clone(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+static int resolve_relative_path(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf sb = STRBUF_INIT;
+       if (argc != 3)
+               die("submodule--helper relative_path takes exactly 2 arguments, got %d", argc);
+
+       printf("%s", relative_path(argv[1], argv[2], &sb));
+       strbuf_release(&sb);
+       return 0;
+}
+
 struct cmd_struct {
        const char *cmd;
        int (*fn)(int, const char **, const char *);
@@ -570,8 +908,11 @@ static struct cmd_struct commands[] = {
        {"list", module_list},
        {"name", module_name},
        {"clone", module_clone},
-       {"sanitize-config", module_sanitize_config},
-       {"update-clone", update_clone}
+       {"update-clone", update_clone},
+       {"relative-path", resolve_relative_path},
+       {"resolve-relative-url", resolve_relative_url},
+       {"resolve-relative-url-test", resolve_relative_url_test},
+       {"init", module_init}
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)