Skip to content

Commit ea27893

Browse files
committed
Merge branch 'pc/submodule-helper-foreach'
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
2 parents 0bf8c8c + fc1b924 commit ea27893

File tree

4 files changed

+190
-47
lines changed

4 files changed

+190
-47
lines changed

Documentation/git-submodule.txt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,17 @@ information too.
183183

184184
foreach [--recursive] <command>::
185185
Evaluates an arbitrary shell command in each checked out submodule.
186-
The command has access to the variables $name, $path, $sha1 and
187-
$toplevel:
186+
The command has access to the variables $name, $sm_path, $displaypath,
187+
$sha1 and $toplevel:
188188
$name is the name of the relevant submodule section in `.gitmodules`,
189-
$path is the name of the submodule directory relative to the
190-
superproject, $sha1 is the commit as recorded in the superproject,
191-
and $toplevel is the absolute path to the top-level of the superproject.
189+
$sm_path is the path of the submodule as recorded in the immediate
190+
superproject, $displaypath contains the relative path from the
191+
current working directory to the submodules root directory,
192+
$sha1 is the commit as recorded in the immediate
193+
superproject, and $toplevel is the absolute path to the top-level
194+
of the immediate superproject.
195+
Note that to avoid conflicts with '$PATH' on Windows, the '$path'
196+
variable is now a deprecated synonym of '$sm_path' variable.
192197
Any submodules defined in the superproject but not checked out are
193198
ignored by this command. Unless given `--quiet`, foreach prints the name
194199
of each submodule before evaluating the command.

builtin/submodule--helper.c

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,149 @@ static void for_each_listed_submodule(const struct module_list *list,
440440
fn(list->entries[i], cb_data);
441441
}
442442

443+
struct cb_foreach {
444+
int argc;
445+
const char **argv;
446+
const char *prefix;
447+
int quiet;
448+
int recursive;
449+
};
450+
#define CB_FOREACH_INIT { 0 }
451+
452+
static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
453+
void *cb_data)
454+
{
455+
struct cb_foreach *info = cb_data;
456+
const char *path = list_item->name;
457+
const struct object_id *ce_oid = &list_item->oid;
458+
459+
const struct submodule *sub;
460+
struct child_process cp = CHILD_PROCESS_INIT;
461+
char *displaypath;
462+
463+
displaypath = get_submodule_displaypath(path, info->prefix);
464+
465+
sub = submodule_from_path(the_repository, &null_oid, path);
466+
467+
if (!sub)
468+
die(_("No url found for submodule path '%s' in .gitmodules"),
469+
displaypath);
470+
471+
if (!is_submodule_populated_gently(path, NULL))
472+
goto cleanup;
473+
474+
prepare_submodule_repo_env(&cp.env_array);
475+
476+
/*
477+
* For the purpose of executing <command> in the submodule,
478+
* separate shell is used for the purpose of running the
479+
* child process.
480+
*/
481+
cp.use_shell = 1;
482+
cp.dir = path;
483+
484+
/*
485+
* NEEDSWORK: the command currently has access to the variables $name,
486+
* $sm_path, $displaypath, $sha1 and $toplevel only when the command
487+
* contains a single argument. This is done for maintaining a faithful
488+
* translation from shell script.
489+
*/
490+
if (info->argc == 1) {
491+
char *toplevel = xgetcwd();
492+
struct strbuf sb = STRBUF_INIT;
493+
494+
argv_array_pushf(&cp.env_array, "name=%s", sub->name);
495+
argv_array_pushf(&cp.env_array, "sm_path=%s", path);
496+
argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath);
497+
argv_array_pushf(&cp.env_array, "sha1=%s",
498+
oid_to_hex(ce_oid));
499+
argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel);
500+
501+
/*
502+
* Since the path variable was accessible from the script
503+
* before porting, it is also made available after porting.
504+
* The environment variable "PATH" has a very special purpose
505+
* on windows. And since environment variables are
506+
* case-insensitive in windows, it interferes with the
507+
* existing PATH variable. Hence, to avoid that, we expose
508+
* path via the args argv_array and not via env_array.
509+
*/
510+
sq_quote_buf(&sb, path);
511+
argv_array_pushf(&cp.args, "path=%s; %s",
512+
sb.buf, info->argv[0]);
513+
strbuf_release(&sb);
514+
free(toplevel);
515+
} else {
516+
argv_array_pushv(&cp.args, info->argv);
517+
}
518+
519+
if (!info->quiet)
520+
printf(_("Entering '%s'\n"), displaypath);
521+
522+
if (info->argv[0] && run_command(&cp))
523+
die(_("run_command returned non-zero status for %s\n."),
524+
displaypath);
525+
526+
if (info->recursive) {
527+
struct child_process cpr = CHILD_PROCESS_INIT;
528+
529+
cpr.git_cmd = 1;
530+
cpr.dir = path;
531+
prepare_submodule_repo_env(&cpr.env_array);
532+
533+
argv_array_pushl(&cpr.args, "--super-prefix", NULL);
534+
argv_array_pushf(&cpr.args, "%s/", displaypath);
535+
argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
536+
NULL);
537+
538+
if (info->quiet)
539+
argv_array_push(&cpr.args, "--quiet");
540+
541+
argv_array_pushv(&cpr.args, info->argv);
542+
543+
if (run_command(&cpr))
544+
die(_("run_command returned non-zero status while"
545+
"recursing in the nested submodules of %s\n."),
546+
displaypath);
547+
}
548+
549+
cleanup:
550+
free(displaypath);
551+
}
552+
553+
static int module_foreach(int argc, const char **argv, const char *prefix)
554+
{
555+
struct cb_foreach info = CB_FOREACH_INIT;
556+
struct pathspec pathspec;
557+
struct module_list list = MODULE_LIST_INIT;
558+
559+
struct option module_foreach_options[] = {
560+
OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")),
561+
OPT_BOOL(0, "recursive", &info.recursive,
562+
N_("Recurse into nested submodules")),
563+
OPT_END()
564+
};
565+
566+
const char *const git_submodule_helper_usage[] = {
567+
N_("git submodule--helper foreach [--quiet] [--recursive] <command>"),
568+
NULL
569+
};
570+
571+
argc = parse_options(argc, argv, prefix, module_foreach_options,
572+
git_submodule_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
573+
574+
if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0)
575+
return 1;
576+
577+
info.argc = argc;
578+
info.argv = argv;
579+
info.prefix = prefix;
580+
581+
for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info);
582+
583+
return 0;
584+
}
585+
443586
struct init_cb {
444587
const char *prefix;
445588
unsigned int flags;
@@ -1876,6 +2019,7 @@ static struct cmd_struct commands[] = {
18762019
{"relative-path", resolve_relative_path, 0},
18772020
{"resolve-relative-url", resolve_relative_url, 0},
18782021
{"resolve-relative-url-test", resolve_relative_url_test, 0},
2022+
{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
18792023
{"init", module_init, SUPPORT_SUPER_PREFIX},
18802024
{"status", module_status, SUPPORT_SUPER_PREFIX},
18812025
{"print-default-remote", print_default_remote, 0},

git-submodule.sh

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -335,45 +335,7 @@ cmd_foreach()
335335
shift
336336
done
337337

338-
toplevel=$(pwd)
339-
340-
# dup stdin so that it can be restored when running the external
341-
# command in the subshell (and a recursive call to this function)
342-
exec 3<&0
343-
344-
{
345-
git submodule--helper list --prefix "$wt_prefix" ||
346-
echo "#unmatched" $?
347-
} |
348-
while read -r mode sha1 stage sm_path
349-
do
350-
die_if_unmatched "$mode" "$sha1"
351-
if test -e "$sm_path"/.git
352-
then
353-
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
354-
say "$(eval_gettext "Entering '\$displaypath'")"
355-
name=$(git submodule--helper name "$sm_path")
356-
(
357-
prefix="$prefix$sm_path/"
358-
sanitize_submodule_env
359-
cd "$sm_path" &&
360-
sm_path=$(git submodule--helper relative-path "$sm_path" "$wt_prefix") &&
361-
# we make $path available to scripts ...
362-
path=$sm_path &&
363-
if test $# -eq 1
364-
then
365-
eval "$1"
366-
else
367-
"$@"
368-
fi &&
369-
if test -n "$recursive"
370-
then
371-
cmd_foreach "--recursive" "$@"
372-
fi
373-
) <&3 3<&- ||
374-
die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")"
375-
fi
376-
done
338+
git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} "$@"
377339
}
378340

379341
#

t/t7407-submodule-foreach.sh

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,16 @@ test_expect_success 'test basic "submodule foreach" usage' '
8282

8383
cat >expect <<EOF
8484
Entering '../sub1'
85-
$pwd/clone-foo1-../sub1-$sub1sha1
85+
$pwd/clone-foo1-sub1-../sub1-$sub1sha1
8686
Entering '../sub3'
87-
$pwd/clone-foo3-../sub3-$sub3sha1
87+
$pwd/clone-foo3-sub3-../sub3-$sub3sha1
8888
EOF
8989

9090
test_expect_success 'test "submodule foreach" from subdirectory' '
9191
mkdir clone/sub &&
9292
(
9393
cd clone/sub &&
94-
git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual
94+
git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$displaypath-\$sha1" >../../actual
9595
) &&
9696
test_i18ncmp expect actual
9797
'
@@ -196,6 +196,38 @@ test_expect_success 'test messages from "foreach --recursive" from subdirectory'
196196
) &&
197197
test_i18ncmp expect actual
198198
'
199+
sub1sha1=$(cd clone2/sub1 && git rev-parse HEAD)
200+
sub2sha1=$(cd clone2/sub2 && git rev-parse HEAD)
201+
sub3sha1=$(cd clone2/sub3 && git rev-parse HEAD)
202+
nested1sha1=$(cd clone2/nested1 && git rev-parse HEAD)
203+
nested2sha1=$(cd clone2/nested1/nested2 && git rev-parse HEAD)
204+
nested3sha1=$(cd clone2/nested1/nested2/nested3 && git rev-parse HEAD)
205+
submodulesha1=$(cd clone2/nested1/nested2/nested3/submodule && git rev-parse HEAD)
206+
207+
cat >expect <<EOF
208+
Entering '../nested1'
209+
toplevel: $pwd/clone2 name: nested1 path: nested1 displaypath: ../nested1 hash: $nested1sha1
210+
Entering '../nested1/nested2'
211+
toplevel: $pwd/clone2/nested1 name: nested2 path: nested2 displaypath: ../nested1/nested2 hash: $nested2sha1
212+
Entering '../nested1/nested2/nested3'
213+
toplevel: $pwd/clone2/nested1/nested2 name: nested3 path: nested3 displaypath: ../nested1/nested2/nested3 hash: $nested3sha1
214+
Entering '../nested1/nested2/nested3/submodule'
215+
toplevel: $pwd/clone2/nested1/nested2/nested3 name: submodule path: submodule displaypath: ../nested1/nested2/nested3/submodule hash: $submodulesha1
216+
Entering '../sub1'
217+
toplevel: $pwd/clone2 name: foo1 path: sub1 displaypath: ../sub1 hash: $sub1sha1
218+
Entering '../sub2'
219+
toplevel: $pwd/clone2 name: foo2 path: sub2 displaypath: ../sub2 hash: $sub2sha1
220+
Entering '../sub3'
221+
toplevel: $pwd/clone2 name: foo3 path: sub3 displaypath: ../sub3 hash: $sub3sha1
222+
EOF
223+
224+
test_expect_success 'test "submodule foreach --recursive" from subdirectory' '
225+
(
226+
cd clone2/untracked &&
227+
git submodule foreach --recursive "echo toplevel: \$toplevel name: \$name path: \$sm_path displaypath: \$displaypath hash: \$sha1" >../../actual
228+
) &&
229+
test_i18ncmp expect actual
230+
'
199231

200232
cat > expect <<EOF
201233
nested1-nested1

0 commit comments

Comments
 (0)