Merge branch 'jk/submodule-init-segv-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 May 2017 05:14:43 +0000 (14:14 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 May 2017 05:14:44 +0000 (14:14 +0900)
Fix a segv in 'submodule init' when url is not given for a submodule.

* jk/submodule-init-segv-fix:
submodule_init: die cleanly on submodules without url defined

1  2 
builtin/submodule--helper.c
t/t7400-submodule-basic.sh
index 36e4231821c0acbb6fe21948c752d4ee7c96405d,b345689561ddf9ce8bb267e9f03d2ebb8500120d..566a5b6a6f8937742e83577918a94f0dcb20c66c
@@@ -95,8 -95,6 +95,8 @@@ static int chop_last_dir(char **remoteu
   * 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://a.com/c   same as previous line, but
 + *                                                   ignore trailing slash in url
   * 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
@@@ -115,8 -113,8 +115,8 @@@ static char *relative_url(const char *r
        struct strbuf sb = STRBUF_INIT;
        size_t len = strlen(remoteurl);
  
 -      if (is_dir_sep(remoteurl[len]))
 -              remoteurl[len] = '\0';
 +      if (is_dir_sep(remoteurl[len-1]))
 +              remoteurl[len-1] = '\0';
  
        if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
                is_relative = 0;
        }
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
 +      if (ends_with(url, "/"))
 +              strbuf_setlen(&sb, sb.len - 1);
        free(remoteurl);
  
        if (starts_with_dot_slash(sb.buf))
@@@ -270,29 -266,6 +270,29 @@@ static int module_list_compute(int argc
        return result;
  }
  
 +static void module_list_active(struct module_list *list)
 +{
 +      int i;
 +      struct module_list active_modules = MODULE_LIST_INIT;
 +
 +      gitmodules_config();
 +
 +      for (i = 0; i < list->nr; i++) {
 +              const struct cache_entry *ce = list->entries[i];
 +
 +              if (!is_submodule_initialized(ce->name))
 +                      continue;
 +
 +              ALLOC_GROW(active_modules.entries,
 +                         active_modules.nr + 1,
 +                         active_modules.alloc);
 +              active_modules.entries[active_modules.nr++] = ce;
 +      }
 +
 +      free(list->entries);
 +      *list = active_modules;
 +}
 +
  static int module_list(int argc, const char **argv, const char *prefix)
  {
        int i;
                if (ce_stage(ce))
                        printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
                else
 -                      printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce));
 +                      printf("%06o %s %d\t", ce->ce_mode,
 +                             oid_to_hex(&ce->oid), ce_stage(ce));
  
                utf8_fprintf(stdout, "%s\n", ce->name);
        }
@@@ -340,12 -312,8 +340,12 @@@ static void init_submodule(const char *
        /* Only loads from .gitmodules, no overlay with .git/config */
        gitmodules_config();
  
 -      if (prefix) {
 -              strbuf_addf(&sb, "%s%s", prefix, path);
 +      if (prefix && get_super_prefix())
 +              die("BUG: cannot have prefix and superprefix");
 +      else if (prefix)
 +              displaypath = xstrdup(relative_path(path, prefix, &sb));
 +      else if (get_super_prefix()) {
 +              strbuf_addf(&sb, "%s%s", get_super_prefix(), path);
                displaypath = strbuf_detach(&sb, NULL);
        } else
                displaypath = xstrdup(path);
                die(_("No url found for submodule path '%s' in .gitmodules"),
                        displaypath);
  
 +      /*
 +       * NEEDSWORK: In a multi-working-tree world, this needs to be
 +       * set in the per-worktree config.
 +       *
 +       * Set active flag for the submodule being initialized
 +       */
 +      if (!is_submodule_initialized(path)) {
 +              strbuf_reset(&sb);
 +              strbuf_addf(&sb, "submodule.%s.active", sub->name);
 +              git_config_set_gently(sb.buf, "true");
 +      }
 +
        /*
         * Copy url setting when it is not set yet.
         * To look up the url in .git/config, we must not fall back to
        strbuf_reset(&sb);
        strbuf_addf(&sb, "submodule.%s.url", sub->name);
        if (git_config_get_string(sb.buf, &url)) {
-               url = xstrdup(sub->url);
-               if (!url)
+               if (!sub->url)
                        die(_("No url found for submodule path '%s' in .gitmodules"),
                                displaypath);
  
+               url = xstrdup(sub->url);
                /* Possibly a url relative to parent */
                if (starts_with_dot_dot_slash(url) ||
                    starts_with_dot_slash(url)) {
                        strbuf_addf(&remotesb, "remote.%s.url", remote);
                        free(remote);
  
 -                      if (git_config_get_string(remotesb.buf, &remoteurl))
 -                              /*
 -                               * The repository is its own
 -                               * authoritative upstream
 -                               */
 +                      if (git_config_get_string(remotesb.buf, &remoteurl)) {
 +                              warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf);
                                remoteurl = xgetcwd();
 +                      }
                        relurl = relative_url(remoteurl, url, NULL);
                        strbuf_release(&remotesb);
                        free(remoteurl);
@@@ -440,6 -398,9 +440,6 @@@ static int module_init(int argc, const 
        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()
        };
        if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
                return 1;
  
 +      /*
 +       * If there are no path args and submodule.active is set then,
 +       * by default, only initialize 'active' modules.
 +       */
 +      if (!argc && git_config_get_value_multi("submodule.active"))
 +              module_list_active(&list);
 +
        for (i = 0; i < list.nr; i++)
                init_submodule(list.entries[i]->name, prefix, quiet);
  
@@@ -488,25 -442,19 +488,25 @@@ static int module_name(int argc, const 
  }
  
  static int clone_submodule(const char *path, const char *gitdir, const char *url,
 -                         const char *depth, const char *reference, int quiet)
 +                         const char *depth, struct string_list *reference,
 +                         int quiet, int progress)
  {
 -      struct child_process cp;
 -      child_process_init(&cp);
 +      struct child_process cp = CHILD_PROCESS_INIT;
  
        argv_array_push(&cp.args, "clone");
        argv_array_push(&cp.args, "--no-checkout");
        if (quiet)
                argv_array_push(&cp.args, "--quiet");
 +      if (progress)
 +              argv_array_push(&cp.args, "--progress");
        if (depth && *depth)
                argv_array_pushl(&cp.args, "--depth", depth, NULL);
 -      if (reference && *reference)
 -              argv_array_pushl(&cp.args, "--reference", reference, NULL);
 +      if (reference->nr) {
 +              struct string_list_item *item;
 +              for_each_string_list_item(item, reference)
 +                      argv_array_pushl(&cp.args, "--reference",
 +                                       item->string, NULL);
 +      }
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
  
        return run_command(&cp);
  }
  
 +struct submodule_alternate_setup {
 +      const char *submodule_name;
 +      enum SUBMODULE_ALTERNATE_ERROR_MODE {
 +              SUBMODULE_ALTERNATE_ERROR_DIE,
 +              SUBMODULE_ALTERNATE_ERROR_INFO,
 +              SUBMODULE_ALTERNATE_ERROR_IGNORE
 +      } error_mode;
 +      struct string_list *reference;
 +};
 +#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
 +      SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
 +
 +static int add_possible_reference_from_superproject(
 +              struct alternate_object_database *alt, void *sas_cb)
 +{
 +      struct submodule_alternate_setup *sas = sas_cb;
 +
 +      /*
 +       * If the alternate object store is another repository, try the
 +       * standard layout with .git/(modules/<name>)+/objects
 +       */
 +      if (ends_with(alt->path, "/objects")) {
 +              char *sm_alternate;
 +              struct strbuf sb = STRBUF_INIT;
 +              struct strbuf err = STRBUF_INIT;
 +              strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects"));
 +
 +              /*
 +               * We need to end the new path with '/' to mark it as a dir,
 +               * otherwise a submodule name containing '/' will be broken
 +               * as the last part of a missing submodule reference would
 +               * be taken as a file name.
 +               */
 +              strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
 +
 +              sm_alternate = compute_alternate_path(sb.buf, &err);
 +              if (sm_alternate) {
 +                      string_list_append(sas->reference, xstrdup(sb.buf));
 +                      free(sm_alternate);
 +              } else {
 +                      switch (sas->error_mode) {
 +                      case SUBMODULE_ALTERNATE_ERROR_DIE:
 +                              die(_("submodule '%s' cannot add alternate: %s"),
 +                                  sas->submodule_name, err.buf);
 +                      case SUBMODULE_ALTERNATE_ERROR_INFO:
 +                              fprintf(stderr, _("submodule '%s' cannot add alternate: %s"),
 +                                      sas->submodule_name, err.buf);
 +                      case SUBMODULE_ALTERNATE_ERROR_IGNORE:
 +                              ; /* nothing */
 +                      }
 +              }
 +              strbuf_release(&sb);
 +      }
 +
 +      return 0;
 +}
 +
 +static void prepare_possible_alternates(const char *sm_name,
 +              struct string_list *reference)
 +{
 +      char *sm_alternate = NULL, *error_strategy = NULL;
 +      struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT;
 +
 +      git_config_get_string("submodule.alternateLocation", &sm_alternate);
 +      if (!sm_alternate)
 +              return;
 +
 +      git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
 +
 +      if (!error_strategy)
 +              error_strategy = xstrdup("die");
 +
 +      sas.submodule_name = sm_name;
 +      sas.reference = reference;
 +      if (!strcmp(error_strategy, "die"))
 +              sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE;
 +      else if (!strcmp(error_strategy, "info"))
 +              sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO;
 +      else if (!strcmp(error_strategy, "ignore"))
 +              sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE;
 +      else
 +              die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy);
 +
 +      if (!strcmp(sm_alternate, "superproject"))
 +              foreach_alt_odb(add_possible_reference_from_superproject, &sas);
 +      else if (!strcmp(sm_alternate, "no"))
 +              ; /* do nothing */
 +      else
 +              die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate);
 +
 +      free(sm_alternate);
 +      free(error_strategy);
 +}
 +
  static int module_clone(int argc, const char **argv, const char *prefix)
  {
 -      const char *name = NULL, *url = NULL;
 -      const char *reference = NULL, *depth = NULL;
 +      const char *name = NULL, *url = NULL, *depth = NULL;
        int quiet = 0;
 -      FILE *submodule_dot_git;
 +      int progress = 0;
        char *p, *path = NULL, *sm_gitdir;
 -      struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
 +      struct string_list reference = STRING_LIST_INIT_NODUP;
 +      char *sm_alternate = NULL, *error_strategy = NULL;
  
        struct option module_clone_options[] = {
                OPT_STRING(0, "prefix", &prefix,
                OPT_STRING(0, "url", &url,
                           N_("string"),
                           N_("url where to clone the submodule from")),
 -              OPT_STRING(0, "reference", &reference,
 -                         N_("string"),
 +              OPT_STRING_LIST(0, "reference", &reference,
 +                         N_("repo"),
                           N_("reference repository")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
                           N_("depth for shallow clones")),
                OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
 +              OPT_BOOL(0, "progress", &progress,
 +                         N_("force cloning progress")),
                OPT_END()
        };
  
                                   module_clone_options);
  
        strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
 -      sm_gitdir = xstrdup(absolute_path(sb.buf));
 +      sm_gitdir = absolute_pathdup(sb.buf);
        strbuf_reset(&sb);
  
        if (!is_absolute_path(path)) {
        if (!file_exists(sm_gitdir)) {
                if (safe_create_leading_directories_const(sm_gitdir) < 0)
                        die(_("could not create directory '%s'"), sm_gitdir);
 -              if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet))
 +
 +              prepare_possible_alternates(name, &reference);
 +
 +              if (clone_submodule(path, sm_gitdir, url, depth, &reference,
 +                                  quiet, progress))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
        } else {
                strbuf_reset(&sb);
        }
  
 -      /* Write a .git file in the submodule to redirect to the superproject. */
 -      strbuf_addf(&sb, "%s/.git", path);
 -      if (safe_create_leading_directories_const(sb.buf) < 0)
 -              die(_("could not create leading directories of '%s'"), sb.buf);
 -      submodule_dot_git = fopen(sb.buf, "w");
 -      if (!submodule_dot_git)
 -              die_errno(_("cannot open file '%s'"), sb.buf);
 -
 -      fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
 -                     relative_path(sm_gitdir, path, &rel_path));
 -      if (fclose(submodule_dot_git))
 -              die(_("could not close file %s"), sb.buf);
 -      strbuf_reset(&sb);
 -      strbuf_reset(&rel_path);
 +      /* Connect module worktree and git dir */
 +      connect_work_tree_and_git_dir(path, sm_gitdir);
  
 -      /* Redirect the worktree of the submodule in the superproject's config */
        p = git_pathdup_submodule(path, "config");
        if (!p)
                die(_("could not get submodule directory for '%s'"), path);
 -      git_config_set_in_file(p, "core.worktree",
 -                             relative_path(path, sm_gitdir, &rel_path));
 +
 +      /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
 +      git_config_get_string("submodule.alternateLocation", &sm_alternate);
 +      if (sm_alternate)
 +              git_config_set_in_file(p, "submodule.alternateLocation",
 +                                         sm_alternate);
 +      git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
 +      if (error_strategy)
 +              git_config_set_in_file(p, "submodule.alternateErrorStrategy",
 +                                         error_strategy);
 +
 +      free(sm_alternate);
 +      free(error_strategy);
 +
        strbuf_release(&sb);
 -      strbuf_release(&rel_path);
        free(sm_gitdir);
        free(path);
        free(p);
@@@ -728,10 -578,8 +728,10 @@@ struct submodule_update_clone 
        struct submodule_update_strategy update;
  
        /* configuration parameters which are passed on to the children */
 +      int progress;
        int quiet;
 -      const char *reference;
 +      int recommend_shallow;
 +      struct string_list references;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
  
        /* 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, 0, -1, STRING_LIST_INIT_DUP, \
 +      NULL, NULL, NULL, \
 +      STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
  
  
  static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@@ -783,13 -626,14 +783,13 @@@ static int prepare_to_clone_next_submod
        struct strbuf displaypath_sb = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        const char *displaypath = NULL;
 -      char *url = NULL;
        int needs_cloning = 0;
  
        if (ce_stage(ce)) {
                if (suc->recursive_prefix)
                        strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name);
                else
 -                      strbuf_addf(&sb, "%s", ce->name);
 +                      strbuf_addstr(&sb, ce->name);
                strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf);
                strbuf_addch(out, '\n');
                goto cleanup;
                goto cleanup;
        }
  
 -      /*
 -       * Looking up the url in .git/config.
 -       * We must not fall back to .gitmodules as we only want
 -       * to process configured submodules.
 -       */
 -      strbuf_reset(&sb);
 -      strbuf_addf(&sb, "submodule.%s.url", sub->name);
 -      git_config_get_string(sb.buf, &url);
 -      if (!url) {
 +      /* Check if the submodule has been initialized. */
 +      if (!is_submodule_initialized(ce->name)) {
                next_submodule_warn_missing(suc, out, displaypath);
                goto cleanup;
        }
  
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode,
 -                      sha1_to_hex(ce->sha1), ce_stage(ce),
 +                      oid_to_hex(&ce->oid), ce_stage(ce),
                        needs_cloning, ce->name);
        string_list_append(&suc->projectlines, sb.buf);
  
        child->err = -1;
        argv_array_push(&child->args, "submodule--helper");
        argv_array_push(&child->args, "clone");
 +      if (suc->progress)
 +              argv_array_push(&child->args, "--progress");
        if (suc->quiet)
                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);
 -      if (suc->reference)
 -              argv_array_push(&child->args, suc->reference);
 +      argv_array_pushl(&child->args, "--url", sub->url, NULL);
 +      if (suc->references.nr) {
 +              struct string_list_item *item;
 +              for_each_string_list_item(item, &suc->references)
 +                      argv_array_pushl(&child->args, "--reference", item->string, NULL);
 +      }
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
  
  cleanup:
 -      free(url);
        strbuf_reset(&displaypath_sb);
        strbuf_reset(&sb);
  
  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)) {
 +                      suc->current ++;
 +                      strbuf_addstr(err, "BUG: submodule considered for "
 +                                         "cloning, doesn't need cloning "
 +                                         "any more?\n");
 +                      return 0;
 +              }
 +              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;
  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->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)
                OPT_STRING(0, "update", &update,
                           N_("string"),
                           N_("rebase, merge, checkout or none")),
 -              OPT_STRING(0, "reference", &suc.reference, N_("repo"),
 +              OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
                              "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_BOOL(0, "progress", &suc.progress,
 +                          N_("force cloning progress")),
                OPT_END()
        };
  
@@@ -1047,176 -835,27 +1047,176 @@@ static int resolve_relative_path(int ar
  {
        struct strbuf sb = STRBUF_INIT;
        if (argc != 3)
 -              die("submodule--helper relative_path takes exactly 2 arguments, got %d", argc);
 +              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;
  }
  
 +static const char *remote_submodule_branch(const char *path)
 +{
 +      const struct submodule *sub;
 +      gitmodules_config();
 +      git_config(submodule_config, NULL);
 +
 +      sub = submodule_from_path(null_sha1, path);
 +      if (!sub)
 +              return NULL;
 +
 +      if (!sub->branch)
 +              return "master";
 +
 +      if (!strcmp(sub->branch, ".")) {
 +              unsigned char sha1[20];
 +              const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
 +
 +              if (!refname)
 +                      die(_("No such ref: %s"), "HEAD");
 +
 +              /* detached HEAD */
 +              if (!strcmp(refname, "HEAD"))
 +                      die(_("Submodule (%s) branch configured to inherit "
 +                            "branch from superproject, but the superproject "
 +                            "is not on any branch"), sub->name);
 +
 +              if (!skip_prefix(refname, "refs/heads/", &refname))
 +                      die(_("Expecting a full ref name, got %s"), refname);
 +              return refname;
 +      }
 +
 +      return sub->branch;
 +}
 +
 +static int resolve_remote_submodule_branch(int argc, const char **argv,
 +              const char *prefix)
 +{
 +      const char *ret;
 +      struct strbuf sb = STRBUF_INIT;
 +      if (argc != 2)
 +              die("submodule--helper remote-branch takes exactly one arguments, got %d", argc);
 +
 +      ret = remote_submodule_branch(argv[1]);
 +      if (!ret)
 +              die("submodule %s doesn't exist", argv[1]);
 +
 +      printf("%s", ret);
 +      strbuf_release(&sb);
 +      return 0;
 +}
 +
 +static int push_check(int argc, const char **argv, const char *prefix)
 +{
 +      struct remote *remote;
 +
 +      if (argc < 2)
 +              die("submodule--helper push-check requires at least 1 argument");
 +
 +      /*
 +       * The remote must be configured.
 +       * This is to avoid pushing to the exact same URL as the parent.
 +       */
 +      remote = pushremote_get(argv[1]);
 +      if (!remote || remote->origin == REMOTE_UNCONFIGURED)
 +              die("remote '%s' not configured", argv[1]);
 +
 +      /* Check the refspec */
 +      if (argc > 2) {
 +              int i, refspec_nr = argc - 2;
 +              struct ref *local_refs = get_local_heads();
 +              struct refspec *refspec = parse_push_refspec(refspec_nr,
 +                                                           argv + 2);
 +
 +              for (i = 0; i < refspec_nr; i++) {
 +                      struct refspec *rs = refspec + i;
 +
 +                      if (rs->pattern || rs->matching)
 +                              continue;
 +
 +                      /*
 +                       * LHS must match a single ref
 +                       * NEEDSWORK: add logic to special case 'HEAD' once
 +                       * working with submodules in a detached head state
 +                       * ceases to be the norm.
 +                       */
 +                      if (count_refspec_match(rs->src, local_refs, NULL) != 1)
 +                              die("src refspec '%s' must name a ref",
 +                                  rs->src);
 +              }
 +              free_refspec(refspec_nr, refspec);
 +      }
 +
 +      return 0;
 +}
 +
 +static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
 +{
 +      int i;
 +      struct pathspec pathspec;
 +      struct module_list list = MODULE_LIST_INIT;
 +      unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES;
 +
 +      struct option embed_gitdir_options[] = {
 +              OPT_STRING(0, "prefix", &prefix,
 +                         N_("path"),
 +                         N_("path into the working tree")),
 +              OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"),
 +                      ABSORB_GITDIR_RECURSE_SUBMODULES),
 +              OPT_END()
 +      };
 +
 +      const char *const git_submodule_helper_usage[] = {
 +              N_("git submodule--helper embed-git-dir [<path>...]"),
 +              NULL
 +      };
 +
 +      argc = parse_options(argc, argv, prefix, embed_gitdir_options,
 +                           git_submodule_helper_usage, 0);
 +
 +      gitmodules_config();
 +      git_config(submodule_config, NULL);
 +
 +      if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
 +              return 1;
 +
 +      for (i = 0; i < list.nr; i++)
 +              absorb_git_dir_into_superproject(prefix,
 +                              list.entries[i]->name, flags);
 +
 +      return 0;
 +}
 +
 +static int is_active(int argc, const char **argv, const char *prefix)
 +{
 +      if (argc != 2)
 +              die("submodule--helper is-active takes exactly 1 argument");
 +
 +      gitmodules_config();
 +
 +      return !is_submodule_initialized(argv[1]);
 +}
 +
 +#define SUPPORT_SUPER_PREFIX (1<<0)
 +
  struct cmd_struct {
        const char *cmd;
        int (*fn)(int, const char **, const char *);
 +      unsigned option;
  };
  
  static struct cmd_struct commands[] = {
 -      {"list", module_list},
 -      {"name", module_name},
 -      {"clone", module_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}
 +      {"list", module_list, 0},
 +      {"name", module_name, 0},
 +      {"clone", module_clone, 0},
 +      {"update-clone", update_clone, 0},
 +      {"relative-path", resolve_relative_path, 0},
 +      {"resolve-relative-url", resolve_relative_url, 0},
 +      {"resolve-relative-url-test", resolve_relative_url_test, 0},
 +      {"init", module_init, SUPPORT_SUPER_PREFIX},
 +      {"remote-branch", resolve_remote_submodule_branch, 0},
 +      {"push-check", push_check, 0},
 +      {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
 +      {"is-active", is_active, 0},
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
                die(_("submodule--helper subcommand must be "
                      "called with a subcommand"));
  
 -      for (i = 0; i < ARRAY_SIZE(commands); i++)
 -              if (!strcmp(argv[1], commands[i].cmd))
 +      for (i = 0; i < ARRAY_SIZE(commands); i++) {
 +              if (!strcmp(argv[1], commands[i].cmd)) {
 +                      if (get_super_prefix() &&
 +                          !(commands[i].option & SUPPORT_SUPER_PREFIX))
 +                              die(_("%s doesn't support --super-prefix"),
 +                                  commands[i].cmd);
                        return commands[i].fn(argc - 1, argv + 1, prefix);
 +              }
 +      }
  
        die(_("'%s' is not a valid submodule--helper "
              "subcommand"), argv[1]);
index c2706fe4723778b68b9673fd85267ea475f791f2,01e25a44c727f2562781bff8cd9ca787cae4ff50..1b8f1dbd3a3d8a59a74888155d36d0d49e94cb64
@@@ -38,6 -38,14 +38,14 @@@ test_expect_success 'submodule update a
        test_i18ngrep "Submodule path .sub. not initialized" actual
  '
  
+ test_expect_success 'submodule update aborts on missing gitmodules url' '
+       test_when_finished "git update-index --remove sub" &&
+       git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub &&
+       test_when_finished "rm -f .gitmodules" &&
+       git config -f .gitmodules submodule.s.path sub &&
+       test_must_fail git submodule init
+ '
  test_expect_success 'configuration parsing' '
        test_when_finished "rm -f .gitmodules" &&
        cat >.gitmodules <<-\EOF &&
@@@ -152,20 -160,6 +160,20 @@@ test_expect_success 'submodule add to .
        )
  '
  
 +test_expect_success 'submodule add to reconfigure existing submodule with --force' '
 +      (
 +              cd addtest-ignore &&
 +              git submodule add --force bogus-url submod &&
 +              git submodule add -b initial "$submodurl" submod-branch &&
 +              test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" &&
 +              test "bogus-url" = "$(git config submodule.submod.url)" &&
 +              # Restore the url
 +              git submodule add --force "$submodurl" submod
 +              test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" &&
 +              test "$submodurl" = "$(git config submodule.submod.url)"
 +      )
 +'
 +
  test_expect_success 'submodule add --branch' '
        echo "refs/heads/initial" >expect-head &&
        cat <<-\EOF >expect-heads &&
@@@ -273,20 -267,6 +281,20 @@@ test_expect_success 'submodule add wit
        test_cmp empty untracked
  '
  
 +test_expect_success 'submodule add with \\ in path' '
 +      test_when_finished "rm -rf parent sub\\with\\backslash" &&
 +
 +      # Initialize a repo with a backslash in its name
 +      git init sub\\with\\backslash &&
 +      touch sub\\with\\backslash/empty.file &&
 +      git -C sub\\with\\backslash add empty.file &&
 +      git -C sub\\with\\backslash commit -m "Added empty.file" &&
 +
 +      # Add that repository as a submodule
 +      git init parent &&
 +      git -C parent submodule add ../sub\\with\\backslash
 +'
 +
  test_expect_success 'submodule add in subdirectory' '
        echo "refs/heads/master" >expect &&
        >empty &&
@@@ -970,7 -950,7 +978,7 @@@ test_expect_success 'submodule deinit f
                cd sub &&
                git submodule deinit ../init >../output
        ) &&
 -      grep "\\.\\./init" output &&
 +      test_i18ngrep "\\.\\./init" output &&
        test -z "$(git config --get-regexp "submodule\.example\.")" &&
        test -n "$(git config --get-regexp "submodule\.example2\.")" &&
        test -f example2/.git &&
@@@ -1144,141 -1124,5 +1152,141 @@@ test_expect_success 'submodule helper l
        test_cmp expect actual
  '
  
 +test_expect_success 'setup superproject with submodules' '
 +      git init sub1 &&
 +      test_commit -C sub1 test &&
 +      test_commit -C sub1 test2 &&
 +      git init multisuper &&
 +      git -C multisuper submodule add ../sub1 sub0 &&
 +      git -C multisuper submodule add ../sub1 sub1 &&
 +      git -C multisuper submodule add ../sub1 sub2 &&
 +      git -C multisuper submodule add ../sub1 sub3 &&
 +      git -C multisuper commit -m "add some submodules"
 +'
 +
 +cat >expect <<-EOF
 +-sub0
 + sub1 (test2)
 + sub2 (test2)
 + sub3 (test2)
 +EOF
 +
 +test_expect_success 'submodule update --init with a specification' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      pwd=$(pwd) &&
 +      git clone file://"$pwd"/multisuper multisuper_clone &&
 +      git -C multisuper_clone submodule update --init . ":(exclude)sub0" &&
 +      git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'submodule update --init with submodule.active set' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      pwd=$(pwd) &&
 +      git clone file://"$pwd"/multisuper multisuper_clone &&
 +      git -C multisuper_clone config submodule.active "." &&
 +      git -C multisuper_clone config --add submodule.active ":(exclude)sub0" &&
 +      git -C multisuper_clone submodule update --init &&
 +      git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'submodule update and setting submodule.<name>.active' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      pwd=$(pwd) &&
 +      git clone file://"$pwd"/multisuper multisuper_clone &&
 +      git -C multisuper_clone config --bool submodule.sub0.active "true" &&
 +      git -C multisuper_clone config --bool submodule.sub1.active "false" &&
 +      git -C multisuper_clone config --bool submodule.sub2.active "true" &&
 +
 +      cat >expect <<-\EOF &&
 +       sub0 (test2)
 +      -sub1
 +       sub2 (test2)
 +      -sub3
 +      EOF
 +      git -C multisuper_clone submodule update &&
 +      git -C multisuper_clone submodule status |cut -c 1,43- >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'clone --recurse-submodules with a pathspec works' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      cat >expected <<-\EOF &&
 +       sub0 (test2)
 +      -sub1
 +      -sub2
 +      -sub3
 +      EOF
 +
 +      git clone --recurse-submodules="sub0" multisuper multisuper_clone &&
 +      git -C multisuper_clone submodule status |cut -c1,43- >actual &&
 +      test_cmp actual expected
 +'
 +
 +test_expect_success 'clone with multiple --recurse-submodules options' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      cat >expect <<-\EOF &&
 +      -sub0
 +       sub1 (test2)
 +      -sub2
 +       sub3 (test2)
 +      EOF
 +
 +      git clone --recurse-submodules="." \
 +                --recurse-submodules=":(exclude)sub0" \
 +                --recurse-submodules=":(exclude)sub2" \
 +                multisuper multisuper_clone &&
 +      git -C multisuper_clone submodule status |cut -c1,43- >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'clone and subsequent updates correctly auto-initialize submodules' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      cat <<-\EOF >expect &&
 +      -sub0
 +       sub1 (test2)
 +      -sub2
 +       sub3 (test2)
 +      EOF
 +
 +      cat <<-\EOF >expect2 &&
 +      -sub0
 +       sub1 (test2)
 +      -sub2
 +       sub3 (test2)
 +      -sub4
 +       sub5 (test2)
 +      EOF
 +
 +      git clone --recurse-submodules="." \
 +                --recurse-submodules=":(exclude)sub0" \
 +                --recurse-submodules=":(exclude)sub2" \
 +                --recurse-submodules=":(exclude)sub4" \
 +                multisuper multisuper_clone &&
 +
 +      git -C multisuper_clone submodule status |cut -c1,43- >actual &&
 +      test_cmp expect actual &&
 +
 +      git -C multisuper submodule add ../sub1 sub4 &&
 +      git -C multisuper submodule add ../sub1 sub5 &&
 +      git -C multisuper commit -m "add more submodules" &&
 +      # obtain the new superproject
 +      git -C multisuper_clone pull &&
 +      git -C multisuper_clone submodule update --init &&
 +      git -C multisuper_clone submodule status |cut -c1,43- >actual &&
 +      test_cmp expect2 actual
 +'
 +
 +test_expect_success 'init properly sets the config' '
 +      test_when_finished "rm -rf multisuper_clone" &&
 +      git clone --recurse-submodules="." \
 +                --recurse-submodules=":(exclude)sub0" \
 +                multisuper multisuper_clone &&
 +
 +      git -C multisuper_clone submodule init -- sub0 sub1 &&
 +      git -C multisuper_clone config --get submodule.sub0.active &&
 +      test_must_fail git -C multisuper_clone config --get submodule.sub1.active
 +'
  
  test_done