Merge branch 'sb/submodule-init'
authorJunio C Hamano <gitster@pobox.com>
Tue, 17 May 2016 21:38:16 +0000 (14:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 17 May 2016 21:38:17 +0000 (14:38 -0700)
Update of "git submodule" to move pieces of logic to C continues.

* sb/submodule-init:
submodule init: redirect stdout to stderr
submodule--helper update-clone: abort gracefully on missing .gitmodules
submodule init: fail gracefully with a missing .gitmodules file
submodule: port init from shell to C
submodule: port resolve_relative_url from shell to C

1  2 
builtin/submodule--helper.c
git-submodule.sh
t/t7400-submodule-basic.sh
index 3bd6883eff842ee139a3d24475401d75f65fe53e,7f0941ddc340583384ef1a463f34360afc16c369..825a421fc928e108fd9b2972c89383533bc0d978
  #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;
@@@ -100,6 -305,125 +305,125 @@@ static int module_list(int argc, const 
        return 0;
  }
  
+ 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;
+       /* Only loads from .gitmodules, no overlay with .git/config */
+       gitmodules_config();
+       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 url found for submodule path '%s' in .gitmodules"),
+                       displaypath);
+       /*
+        * 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);
+               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);
+ }
+ static int module_init(int argc, const char **argv, const char *prefix)
+ {
+       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 module_name(int argc, const char **argv, const char *prefix)
  {
        const struct submodule *sub;
  
        return 0;
  }
 +
 +/*
 + * 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)
 +{
 +      if (starts_with(var, "credential."))
 +              return 1;
 +      return 0;
 +}
 +
 +static int sanitize_submodule_config(const char *var, const char *value, void *data)
 +{
 +      struct strbuf *out = data;
 +
 +      if (submodule_config_ok(var)) {
 +              if (out->len)
 +                      strbuf_addch(out, ' ');
 +
 +              if (value)
 +                      sq_quotef(out, "%s=%s", var, value);
 +              else
 +                      sq_quote_buf(out, var);
 +      }
 +
 +      return 0;
 +}
 +
 +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);
 +              }
 +      }
 +
 +}
 +
  static int clone_submodule(const char *path, const char *gitdir, const char *url,
                           const char *depth, const char *reference, int quiet)
  {
        argv_array_push(&cp.args, path);
  
        cp.git_cmd = 1;
 -      cp.env = local_repo_env;
 +      prepare_submodule_repo_env(&cp.env_array);
        cp.no_stdin = 1;
  
        return run_command(&cp);
@@@ -229,17 -504,16 +553,17 @@@ static int module_clone(int argc, cons
  
        const char *const git_submodule_helper_usage[] = {
                N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
 -                 "[--reference <repository>] [--name <name>] [--url <url>]"
 -                 "[--depth <depth>] [--] [<path>...]"),
 +                 "[--reference <repository>] [--name <name>] [--depth <depth>] "
 +                 "--url <url> --path <path>"),
                NULL
        };
  
        argc = parse_options(argc, argv, prefix, module_clone_options,
                             git_submodule_helper_usage, 0);
  
 -      if (!path || !*path)
 -              die(_("submodule--helper: unspecified or empty --path"));
 +      if (argc || !url || !path || !*path)
 +              usage_with_options(git_submodule_helper_usage,
 +                                 module_clone_options);
  
        strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
        sm_gitdir = xstrdup(absolute_path(sb.buf));
        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;
        SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 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
   * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise.
@@@ -370,6 -647,11 +713,11 @@@ static int prepare_to_clone_next_submod
        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)) {
        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;
        }
  
@@@ -570,8 -840,10 +906,11 @@@ static struct cmd_struct commands[] = 
        {"list", module_list},
        {"name", module_name},
        {"clone", module_clone},
-       {"update-clone", update_clone}
 +      {"sanitize-config", module_sanitize_config},
+       {"update-clone", update_clone},
+       {"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)
diff --combined git-submodule.sh
index 2a84d7e66a0e6c2bbd0bee1c3e796e4580feaea9,82e95a923bd94f1da578f56e909da59594259170..14d02cc4bc2d55c63f5a49fa78b599e8e649e6a3
@@@ -46,79 -46,6 +46,6 @@@ prefix
  custom_name=
  depth=
  
- # The function takes at most 2 arguments. The first argument is the
- # URL that navigates to the submodule origin repo. When relative, this URL
- # is relative to the superproject origin URL repo. The second up_path
- # argument, if specified, is the relative path that navigates
- # from the submodule working tree to the superproject working tree.
- #
- # The output of the function is the origin URL of the submodule.
- #
- # The output will either be 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.
- resolve_relative_url ()
- {
-       remote=$(get_default_remote)
-       remoteurl=$(git config "remote.$remote.url") ||
-               remoteurl=$(pwd) # the repository is its own authoritative upstream
-       url="$1"
-       remoteurl=${remoteurl%/}
-       sep=/
-       up_path="$2"
-       case "$remoteurl" in
-       *:*|/*)
-               is_relative=
-               ;;
-       ./*|../*)
-               is_relative=t
-               ;;
-       *)
-               is_relative=t
-               remoteurl="./$remoteurl"
-               ;;
-       esac
-       while test -n "$url"
-       do
-               case "$url" in
-               ../*)
-                       url="${url#../}"
-                       case "$remoteurl" in
-                       */*)
-                               remoteurl="${remoteurl%/*}"
-                               ;;
-                       *:*)
-                               remoteurl="${remoteurl%:*}"
-                               sep=:
-                               ;;
-                       *)
-                               if test -z "$is_relative" || test "." = "$remoteurl"
-                               then
-                                       die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
-                               else
-                                       remoteurl=.
-                               fi
-                               ;;
-                       esac
-                       ;;
-               ./*)
-                       url="${url#./}"
-                       ;;
-               *)
-                       break;;
-               esac
-       done
-       remoteurl="$remoteurl$sep${url%/}"
-       echo "${is_relative:+${up_path}}${remoteurl#./}"
- }
  # Resolve a path to be relative to another path.  This is intended for
  # converting submodule paths when git-submodule is run in a subdirectory
  # and only handles paths where the directory separator is '/'.
@@@ -192,16 -119,6 +119,16 @@@ isnumber(
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
  }
  
 +# Sanitize the local git environment for use within a submodule. We
 +# can't simply use clear_local_git_env since we want to preserve some
 +# of the settings from GIT_CONFIG_PARAMETERS.
 +sanitize_submodule_env()
 +{
 +      sanitized_config=$(git submodule--helper sanitize-config)
 +      clear_local_git_env
 +      GIT_CONFIG_PARAMETERS=$sanitized_config
 +}
 +
  #
  # Add a new submodule to the working tree, .gitmodules and the index
  #
@@@ -291,7 -208,7 +218,7 @@@ cmd_add(
                die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
  
                # dereference source url relative to parent's url
-               realrepo=$(resolve_relative_url "$repo") || exit
+               realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
                ;;
        *:*|/*)
                # absolute url
@@@ -357,9 -274,9 +284,9 @@@ Use -f if you really want to add it." >
                                echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
                        fi
                fi
 -              git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit
 +              git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
                (
 -                      clear_local_git_env
 +                      sanitize_submodule_env
                        cd "$sm_path" &&
                        # ash fails to wordsplit ${branch:+-b "$branch"...}
                        case "$branch" in
@@@ -428,7 -345,7 +355,7 @@@ cmd_foreach(
                        name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
 -                              clear_local_git_env
 +                              sanitize_submodule_env
                                cd "$sm_path" &&
                                sm_path=$(relative_path "$sm_path") &&
                                # we make $path available to scripts ...
@@@ -477,50 -394,7 +404,7 @@@ cmd_init(
                shift
        done
  
-       git submodule--helper list --prefix "$wt_prefix" "$@" |
-       while read mode sha1 stage sm_path
-       do
-               die_if_unmatched "$mode"
-               name=$(git submodule--helper name "$sm_path") || exit
-               displaypath=$(relative_path "$prefix$sm_path")
-               # Copy url setting when it is not set yet
-               if test -z "$(git config "submodule.$name.url")"
-               then
-                       url=$(git config -f .gitmodules submodule."$name".url)
-                       test -z "$url" &&
-                       die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")"
-                       # Possibly a url relative to parent
-                       case "$url" in
-                       ./*|../*)
-                               url=$(resolve_relative_url "$url") || exit
-                               ;;
-                       esac
-                       git config submodule."$name".url "$url" ||
-                       die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")"
-                       say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")"
-               fi
-               # Copy "update" setting when it is not set yet
-               if upd="$(git config -f .gitmodules submodule."$name".update)" &&
-                  test -n "$upd" &&
-                  test -z "$(git config submodule."$name".update)"
-               then
-                       case "$upd" in
-                       checkout | rebase | merge | none)
-                               ;; # known modes of updating
-                       *)
-                               echo >&2 "warning: unknown update mode '$upd' suggested for submodule '$name'"
-                               upd=none
-                               ;;
-                       esac
-                       git config submodule."$name".update "$upd" ||
-                       die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")"
-               fi
-       done
+       git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} "$@"
  }
  
  #
@@@ -602,14 -476,14 +486,14 @@@ cmd_deinit(
  }
  
  is_tip_reachable () (
 -      clear_local_git_env
 +      sanitize_submodule_env &&
        cd "$1" &&
        rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
        test -z "$rev"
  )
  
  fetch_in_submodule () (
 -      clear_local_git_env
 +      sanitize_submodule_env &&
        cd "$1" &&
        case "$2" in
        '')
@@@ -736,7 -610,7 +620,7 @@@ cmd_update(
                        subsha1=
                        update_module=checkout
                else
 -                      subsha1=$(clear_local_git_env; cd "$sm_path" &&
 +                      subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
                        die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
                fi
                        if test -z "$nofetch"
                        then
                                # Fetch remote before determining tracking $sha1
 -                              (clear_local_git_env; cd "$sm_path" && git-fetch) ||
 +                              (sanitize_submodule_env; cd "$sm_path" && git-fetch) ||
                                die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
                        fi
 -                      remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote)
 -                      sha1=$(clear_local_git_env; cd "$sm_path" &&
 +                      remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote)
 +                      sha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify "${remote_name}/${branch}") ||
                        die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
                fi
                                die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")"
                        esac
  
 -                      if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
 +                      if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
                        then
                                say "$say_msg"
                        elif test -n "$must_die_on_failure"
                if test -n "$recursive"
                then
                        (
-                               prefix="$prefix$sm_path/"
+                               prefix=$(relative_path "$prefix$sm_path/")
+                               wt_prefix=
 -                              clear_local_git_env
 +                              sanitize_submodule_env
                                cd "$sm_path" &&
                                eval cmd_update
                        )
  
  set_name_rev () {
        revname=$( (
 -              clear_local_git_env
 +              sanitize_submodule_env
                cd "$1" && {
                        git describe "$2" 2>/dev/null ||
                        git describe --tags "$2" 2>/dev/null ||
@@@ -1146,7 -1021,7 +1031,7 @@@ cmd_status(
                else
                        if test -z "$cached"
                        then
 -                              sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
 +                              sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD)
                        fi
                        set_name_rev "$sm_path" "$sha1"
                        say "+$sha1 $displaypath$revname"
                then
                        (
                                prefix="$displaypath/"
 -                              clear_local_git_env
 +                              sanitize_submodule_env
                                wt_prefix=
                                cd "$sm_path" &&
                                eval cmd_status
@@@ -1212,9 -1087,9 +1097,9 @@@ cmd_sync(
                        # guarantee a trailing /
                        up_path=${up_path%/}/ &&
                        # path from submodule work tree to submodule origin repo
-                       sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
+                       sub_origin_url=$(git submodule--helper resolve-relative-url "$url" "$up_path") &&
                        # path from superproject work tree to submodule origin repo
-                       super_config_url=$(resolve_relative_url "$url") || exit
+                       super_config_url=$(git submodule--helper resolve-relative-url "$url") || exit
                        ;;
                *)
                        sub_origin_url="$url"
                        if test -e "$sm_path"/.git
                        then
                        (
 -                              clear_local_git_env
 +                              sanitize_submodule_env
                                cd "$sm_path"
                                remote=$(get_default_remote)
                                git config remote."$remote".url "$sub_origin_url"
index d48d63a6fd0d3f4480d3b42ae146326e7b11a56a,814ee63194bbf630b1cd5493acbb9d6ba882cb41..90d80d369c7e9c35f7f86b50e660b95cd279ccaa
@@@ -18,6 -18,22 +18,22 @@@ test_expect_success 'setup - initial co
        git branch initial
  '
  
+ test_expect_success 'submodule init aborts on missing .gitmodules file' '
+       test_when_finished "git update-index --remove sub" &&
+       git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub &&
+       # missing the .gitmodules file here
+       test_must_fail git submodule init 2>actual &&
+       test_i18ngrep "No url found for submodule path" actual
+ '
+ test_expect_success 'submodule update aborts on missing .gitmodules file' '
+       test_when_finished "git update-index --remove sub" &&
+       git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub &&
+       # missing the .gitmodules file here
+       git submodule update sub 2>actual &&
+       test_i18ngrep "Submodule path .sub. not initialized" actual
+ '
  test_expect_success 'configuration parsing' '
        test_when_finished "rm -f .gitmodules" &&
        cat >.gitmodules <<-\EOF &&
@@@ -898,7 -914,7 +914,7 @@@ test_expect_success 'submodule deinit w
                git init &&
                >file &&
                git add file &&
 -              git commit -m "repo should not be empty"
 +              git commit -m "repo should not be empty" &&
                git submodule deinit .
        )
  '