config: add config_from_gitmodules
[gitweb.git] / builtin / submodule--helper.c
index 704df874d168cf43aa465448af4b65cc193a3689..6abdad3294ce84652a55522aff55f6e7673d192f 100644 (file)
@@ -1,5 +1,7 @@
 #include "builtin.h"
+#include "repository.h"
 #include "cache.h"
+#include "config.h"
 #include "parse-options.h"
 #include "quote.h"
 #include "pathspec.h"
@@ -94,6 +96,8 @@ static int chop_last_dir(char **remoteurl, int is_relative)
  * 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
@@ -112,8 +116,8 @@ static char *relative_url(const char *remote_url,
        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;
@@ -146,6 +150,8 @@ static char *relative_url(const char *remote_url,
        }
        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))
@@ -228,8 +234,7 @@ static int module_list_compute(int argc, const char **argv,
        int i, result = 0;
        char *ps_matched = NULL;
        parse_pathspec(pathspec, 0,
-                      PATHSPEC_PREFER_FULL |
-                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      PATHSPEC_PREFER_FULL,
                       prefix, argv);
 
        if (pathspec->nr)
@@ -265,6 +270,29 @@ static int module_list_compute(int argc, const char **argv,
        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_active(the_repository, 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;
@@ -295,7 +323,8 @@ static int module_list(int argc, const char **argv, const char *prefix)
                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));
 
                fprintf(stdout, "%s\n", ce->name);
        }
@@ -311,8 +340,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
        /* 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);
@@ -323,6 +356,18 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
                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_active(the_repository, 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
@@ -331,12 +376,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
        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)) {
@@ -346,12 +391,10 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
                        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);
@@ -397,9 +440,6 @@ static int module_init(int argc, const char **argv, const char *prefix)
        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()
        };
@@ -415,6 +455,13 @@ static int module_init(int argc, const char **argv, const char *prefix)
        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);
 
@@ -441,7 +488,8 @@ static int module_name(int argc, const char **argv, const char *prefix)
 }
 
 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;
 
@@ -449,10 +497,16 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
        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);
 
@@ -466,15 +520,109 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
        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,
@@ -489,13 +637,15 @@ static int module_clone(int argc, const char **argv, const char *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()
        };
 
@@ -514,7 +664,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                                   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)) {
@@ -526,7 +676,11 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        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 {
@@ -537,29 +691,27 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                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);
@@ -576,9 +728,10 @@ struct submodule_update_clone {
        struct submodule_update_strategy update;
 
        /* configuration parameters which are passed on to the children */
+       int progress;
        int quiet;
        int recommend_shallow;
-       const char *reference;
+       struct string_list references;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
@@ -594,7 +747,8 @@ struct submodule_update_clone {
        int failed_clones_nr, failed_clones_alloc;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
+       NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
 
 
@@ -629,7 +783,6 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        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)) {
@@ -663,15 +816,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                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_active(the_repository, ce->name)) {
                next_submodule_warn_missing(suc, out, displaypath);
                goto cleanup;
        }
@@ -682,7 +828,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 
        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);
 
@@ -695,6 +841,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        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)
@@ -703,14 +851,16 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                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);
 
@@ -829,7 +979,7 @@ 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 "
@@ -839,6 +989,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
                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()
        };
 
@@ -953,33 +1105,135 @@ static int resolve_remote_submodule_branch(int argc, const char **argv,
        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_active(the_repository, 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},
-       {"remote-branch", resolve_remote_submodule_branch}
+       {"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)
 {
        int i;
-       if (argc < 2)
-               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))
+       if (argc < 2 || !strcmp(argv[1], "-h"))
+               usage("git submodule--helper <command>");
+
+       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]);