Merge branch 'pc/submodule-helper-foreach'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:35 +0000 (13:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:35 +0000 (13:22 -0700)
The bulk of "git submodule foreach" has been rewritten in C.

* pc/submodule-helper-foreach:
  submodule: port submodule subcommand 'foreach' from shell to C
  submodule foreach: document variable '$displaypath'
  submodule foreach: document '$sm_path' instead of '$path'
  submodule foreach: correct '$path' in nested submodules from a subdirectory

1  2 
Documentation/git-submodule.txt
builtin/submodule--helper.c
git-submodule.sh

@@@ -42,8 -42,8 +42,8 @@@ have to use '../foo.git' instead of './
  when following the rules for relative URLs - because the evaluation
  of relative URLs in Git is identical to that of relative directories).
  +
 -The default remote is the remote of the remote tracking branch
 -of the current branch. If no such remote tracking branch exists or
 +The default remote is the remote of the remote-tracking branch
 +of the current branch. If no such remote-tracking branch exists or
  the HEAD is detached, "origin" is assumed to be the default remote.
  If the superproject doesn't have a default remote configured
  the superproject is its own authoritative upstream and the current
@@@ -183,12 -183,17 +183,17 @@@ information too
  
  foreach [--recursive] <command>::
        Evaluates an arbitrary shell command in each checked out submodule.
-       The command has access to the variables $name, $path, $sha1 and
-       $toplevel:
+       The command has access to the variables $name, $sm_path, $displaypath,
+       $sha1 and $toplevel:
        $name is the name of the relevant submodule section in `.gitmodules`,
-       $path is the name of the submodule directory relative to the
-       superproject, $sha1 is the commit as recorded in the superproject,
-       and $toplevel is the absolute path to the top-level of the superproject.
+       $sm_path is the path of the submodule as recorded in the immediate
+       superproject, $displaypath contains the relative path from the
+       current working directory to the submodules root directory,
+       $sha1 is the commit as recorded in the immediate
+       superproject, and $toplevel is the absolute path to the top-level
+       of the immediate superproject.
+       Note that to avoid conflicts with '$PATH' on Windows, the '$path'
+       variable is now a deprecated synonym of '$sm_path' variable.
        Any submodules defined in the superproject but not checked out are
        ignored by this command. Unless given `--quiet`, foreach prints the name
        of each submodule before evaluating the command.
@@@ -239,13 -244,6 +244,13 @@@ OPTION
  --quiet::
        Only print error messages.
  
 +--progress::
 +      This option is only valid for add and update commands.
 +      Progress status is reported on the standard error stream
 +      by default when it is attached to a terminal, unless -q
 +      is specified. This flag forces progress status even if the
 +      standard error stream is not directed to a terminal.
 +
  --all::
        This option is only valid for the deinit command. Unregister all
        submodules in the working tree.
@@@ -369,15 -367,7 +374,15 @@@ the submodule itself
        this option will be passed to the linkgit:git-clone[1] command.
  +
  *NOTE*: Do *not* use this option unless you have read the note
 -for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
 +for linkgit:git-clone[1]'s `--reference`, `--shared`, and `--dissociate`
 +options carefully.
 +
 +--dissociate::
 +      This option is only valid for add and update commands.  These
 +      commands sometimes need to clone a remote repository. In this case,
 +      this option will be passed to the linkgit:git-clone[1] command.
 ++
 +*NOTE*: see the NOTE for the `--reference` option.
  
  --recursive::
        This option is only valid for foreach, update, status and sync commands.
@@@ -12,7 -12,6 +12,7 @@@
  #include "run-command.h"
  #include "remote.h"
  #include "refs.h"
 +#include "refspec.h"
  #include "connect.h"
  #include "revision.h"
  #include "diffcore.h"
@@@ -440,6 -439,149 +440,149 @@@ static void for_each_listed_submodule(c
                fn(list->entries[i], cb_data);
  }
  
+ struct cb_foreach {
+       int argc;
+       const char **argv;
+       const char *prefix;
+       int quiet;
+       int recursive;
+ };
+ #define CB_FOREACH_INIT { 0 }
+ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
+                                      void *cb_data)
+ {
+       struct cb_foreach *info = cb_data;
+       const char *path = list_item->name;
+       const struct object_id *ce_oid = &list_item->oid;
+       const struct submodule *sub;
+       struct child_process cp = CHILD_PROCESS_INIT;
+       char *displaypath;
+       displaypath = get_submodule_displaypath(path, info->prefix);
+       sub = submodule_from_path(the_repository, &null_oid, path);
+       if (!sub)
+               die(_("No url found for submodule path '%s' in .gitmodules"),
+                       displaypath);
+       if (!is_submodule_populated_gently(path, NULL))
+               goto cleanup;
+       prepare_submodule_repo_env(&cp.env_array);
+       /*
+        * For the purpose of executing <command> in the submodule,
+        * separate shell is used for the purpose of running the
+        * child process.
+        */
+       cp.use_shell = 1;
+       cp.dir = path;
+       /*
+        * NEEDSWORK: the command currently has access to the variables $name,
+        * $sm_path, $displaypath, $sha1 and $toplevel only when the command
+        * contains a single argument. This is done for maintaining a faithful
+        * translation from shell script.
+        */
+       if (info->argc == 1) {
+               char *toplevel = xgetcwd();
+               struct strbuf sb = STRBUF_INIT;
+               argv_array_pushf(&cp.env_array, "name=%s", sub->name);
+               argv_array_pushf(&cp.env_array, "sm_path=%s", path);
+               argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath);
+               argv_array_pushf(&cp.env_array, "sha1=%s",
+                               oid_to_hex(ce_oid));
+               argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel);
+               /*
+                * Since the path variable was accessible from the script
+                * before porting, it is also made available after porting.
+                * The environment variable "PATH" has a very special purpose
+                * on windows. And since environment variables are
+                * case-insensitive in windows, it interferes with the
+                * existing PATH variable. Hence, to avoid that, we expose
+                * path via the args argv_array and not via env_array.
+                */
+               sq_quote_buf(&sb, path);
+               argv_array_pushf(&cp.args, "path=%s; %s",
+                                sb.buf, info->argv[0]);
+               strbuf_release(&sb);
+               free(toplevel);
+       } else {
+               argv_array_pushv(&cp.args, info->argv);
+       }
+       if (!info->quiet)
+               printf(_("Entering '%s'\n"), displaypath);
+       if (info->argv[0] && run_command(&cp))
+               die(_("run_command returned non-zero status for %s\n."),
+                       displaypath);
+       if (info->recursive) {
+               struct child_process cpr = CHILD_PROCESS_INIT;
+               cpr.git_cmd = 1;
+               cpr.dir = path;
+               prepare_submodule_repo_env(&cpr.env_array);
+               argv_array_pushl(&cpr.args, "--super-prefix", NULL);
+               argv_array_pushf(&cpr.args, "%s/", displaypath);
+               argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
+                               NULL);
+               if (info->quiet)
+                       argv_array_push(&cpr.args, "--quiet");
+               argv_array_pushv(&cpr.args, info->argv);
+               if (run_command(&cpr))
+                       die(_("run_command returned non-zero status while"
+                               "recursing in the nested submodules of %s\n."),
+                               displaypath);
+       }
+ cleanup:
+       free(displaypath);
+ }
+ static int module_foreach(int argc, const char **argv, const char *prefix)
+ {
+       struct cb_foreach info = CB_FOREACH_INIT;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       struct option module_foreach_options[] = {
+               OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")),
+               OPT_BOOL(0, "recursive", &info.recursive,
+                        N_("Recurse into nested submodules")),
+               OPT_END()
+       };
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper foreach [--quiet] [--recursive] <command>"),
+               NULL
+       };
+       argc = parse_options(argc, argv, prefix, module_foreach_options,
+                            git_submodule_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
+       if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0)
+               return 1;
+       info.argc = argc;
+       info.argv = argv;
+       info.prefix = prefix;
+       for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info);
+       return 0;
+ }
  struct init_cb {
        const char *prefix;
        unsigned int flags;
@@@ -1066,7 -1208,7 +1209,7 @@@ static int module_deinit(int argc, cons
  }
  
  static int clone_submodule(const char *path, const char *gitdir, const char *url,
 -                         const char *depth, struct string_list *reference,
 +                         const char *depth, struct string_list *reference, int dissociate,
                           int quiet, int progress)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
                        argv_array_pushl(&cp.args, "--reference",
                                         item->string, NULL);
        }
 +      if (dissociate)
 +              argv_array_push(&cp.args, "--dissociate");
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
  
@@@ -1202,7 -1342,6 +1345,7 @@@ static int module_clone(int argc, cons
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
 +      int dissociate = 0;
        char *sm_alternate = NULL, *error_strategy = NULL;
  
        struct option module_clone_options[] = {
                OPT_STRING_LIST(0, "reference", &reference,
                           N_("repo"),
                           N_("reference repository")),
 +              OPT_BOOL(0, "dissociate", &dissociate,
 +                         N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
                           N_("depth for shallow clones")),
  
                prepare_possible_alternates(name, &reference);
  
 -              if (clone_submodule(path, sm_gitdir, url, depth, &reference,
 +              if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
                                    quiet, progress))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
@@@ -1314,7 -1451,6 +1457,7 @@@ struct submodule_update_clone 
        int quiet;
        int recommend_shallow;
        struct string_list references;
 +      int dissociate;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
        int failed_clones_nr, failed_clones_alloc;
  };
  #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
 -      SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
 +      SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
        NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
  
@@@ -1457,8 -1593,6 +1600,8 @@@ static int prepare_to_clone_next_submod
                for_each_string_list_item(item, &suc->references)
                        argv_array_pushl(&child->args, "--reference", item->string, NULL);
        }
 +      if (suc->dissociate)
 +              argv_array_push(&child->args, "--dissociate");
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
  
@@@ -1592,8 -1726,6 +1735,8 @@@ static int update_clone(int argc, cons
                           N_("rebase, merge, checkout or none")),
                OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
 +              OPT_BOOL(0, "dissociate", &suc.dissociate,
 +                         N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
                              "specified number of revisions")),
@@@ -1754,14 -1886,13 +1897,14 @@@ static int push_check(int argc, const c
  
        /* Check the refspec */
        if (argc > 2) {
 -              int i, refspec_nr = argc - 2;
 +              int i;
                struct ref *local_refs = get_local_heads();
 -              struct refspec *refspec = parse_push_refspec(refspec_nr,
 -                                                           argv + 2);
 +              struct refspec refspec = REFSPEC_INIT_PUSH;
  
 -              for (i = 0; i < refspec_nr; i++) {
 -                      struct refspec *rs = refspec + i;
 +              refspec_appendn(&refspec, argv + 2, argc - 2);
 +
 +              for (i = 0; i < refspec.nr; i++) {
 +                      const struct refspec_item *rs = &refspec.items[i];
  
                        if (rs->pattern || rs->matching)
                                continue;
                                    rs->src);
                        }
                }
 -              free_refspec(refspec_nr, refspec);
 +              refspec_clear(&refspec);
        }
        free(head);
  
@@@ -1837,29 -1968,6 +1980,29 @@@ static int is_active(int argc, const ch
        return !is_submodule_active(the_repository, argv[1]);
  }
  
 +/*
 + * Exit non-zero if any of the submodule names given on the command line is
 + * invalid. If no names are given, filter stdin to print only valid names
 + * (which is primarily intended for testing).
 + */
 +static int check_name(int argc, const char **argv, const char *prefix)
 +{
 +      if (argc > 1) {
 +              while (*++argv) {
 +                      if (check_submodule_name(*argv) < 0)
 +                              return 1;
 +              }
 +      } else {
 +              struct strbuf buf = STRBUF_INIT;
 +              while (strbuf_getline(&buf, stdin) != EOF) {
 +                      if (!check_submodule_name(buf.buf))
 +                              printf("%s\n", buf.buf);
 +              }
 +              strbuf_release(&buf);
 +      }
 +      return 0;
 +}
 +
  #define SUPPORT_SUPER_PREFIX (1<<0)
  
  struct cmd_struct {
@@@ -1876,6 -1984,7 +2019,7 @@@ static struct cmd_struct commands[] = 
        {"relative-path", resolve_relative_path, 0},
        {"resolve-relative-url", resolve_relative_url, 0},
        {"resolve-relative-url-test", resolve_relative_url_test, 0},
+       {"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
        {"init", module_init, SUPPORT_SUPER_PREFIX},
        {"status", module_status, SUPPORT_SUPER_PREFIX},
        {"print-default-remote", print_default_remote, 0},
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
        {"is-active", is_active, 0},
 +      {"check-name", check_name, 0},
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --combined git-submodule.sh
@@@ -42,7 -42,6 +42,7 @@@ prefix
  custom_name=
  depth=
  progress=
 +dissociate=
  
  die_if_unmatched ()
  {
@@@ -118,9 -117,6 +118,9 @@@ cmd_add(
                -q|--quiet)
                        GIT_QUIET=1
                        ;;
 +              --progress)
 +                      progress=1
 +                      ;;
                --reference)
                        case "$2" in '') usage ;; esac
                        reference_path=$2
                --reference=*)
                        reference_path="${1#--reference=}"
                        ;;
 +              --dissociate)
 +                      dissociate=1
 +                      ;;
                --name)
                        case "$2" in '') usage ;; esac
                        custom_name=$2
@@@ -236,11 -229,6 +236,11 @@@ Use -f if you really want to add it." >
                sm_name="$sm_path"
        fi
  
 +      if ! git submodule--helper check-name "$sm_name"
 +      then
 +              die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
 +      fi
 +
        # perhaps the path exists and is already a git repo, else clone it
        if test -e "$sm_path"
        then
@@@ -267,7 -255,7 +267,7 @@@ or you are unsure what this means choos
                                eval_gettextln "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:+"$reference"} ${depth:+"$depth"} || exit
 +              git submodule--helper clone ${GIT_QUIET:+--quiet} ${progress:+"--progress"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
                (
                        sanitize_submodule_env
                        cd "$sm_path" &&
@@@ -335,45 -323,7 +335,7 @@@ cmd_foreach(
                shift
        done
  
-       toplevel=$(pwd)
-       # dup stdin so that it can be restored when running the external
-       # command in the subshell (and a recursive call to this function)
-       exec 3<&0
-       {
-               git submodule--helper list --prefix "$wt_prefix" ||
-               echo "#unmatched" $?
-       } |
-       while read -r mode sha1 stage sm_path
-       do
-               die_if_unmatched "$mode" "$sha1"
-               if test -e "$sm_path"/.git
-               then
-                       displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-                       say "$(eval_gettext "Entering '\$displaypath'")"
-                       name=$(git submodule--helper name "$sm_path")
-                       (
-                               prefix="$prefix$sm_path/"
-                               sanitize_submodule_env
-                               cd "$sm_path" &&
-                               sm_path=$(git submodule--helper relative-path "$sm_path" "$wt_prefix") &&
-                               # we make $path available to scripts ...
-                               path=$sm_path &&
-                               if test $# -eq 1
-                               then
-                                       eval "$1"
-                               else
-                                       "$@"
-                               fi &&
-                               if test -n "$recursive"
-                               then
-                                       cmd_foreach "--recursive" "$@"
-                               fi
-                       ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")"
-               fi
-       done
+       git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} "$@"
  }
  
  #
@@@ -477,7 -427,7 +439,7 @@@ cmd_update(
                        GIT_QUIET=1
                        ;;
                --progress)
 -                      progress="--progress"
 +                      progress=1
                        ;;
                -i|--init)
                        init=1
                --reference=*)
                        reference="$1"
                        ;;
 +              --dissociate)
 +                      dissociate=1
 +                      ;;
                -m|--merge)
                        update="merge"
                        ;;
  
        {
        git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
 -              ${progress:+"$progress"} \
 +              ${progress:+"--progress"} \
                ${wt_prefix:+--prefix "$wt_prefix"} \
                ${prefix:+--recursive-prefix "$prefix"} \
                ${update:+--update "$update"} \
                ${reference:+"$reference"} \
 +              ${dissociate:+"--dissociate"} \
                ${depth:+--depth "$depth"} \
 -              ${recommend_shallow:+"$recommend_shallow"} \
 -              ${jobs:+$jobs} \
 +              $recommend_shallow \
 +              $jobs \
                "$@" || echo "#unmatched" $?
        } | {
        err=
                                # is not reachable from a ref.
                                is_tip_reachable "$sm_path" "$sha1" ||
                                fetch_in_submodule "$sm_path" $depth ||
 -                              die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
 +                              say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
  
                                # Now we tried the usual fetch, but $sha1 may
                                # not be reachable from any of the refs