submodule: port submodule subcommand 'foreach' from shell to C

This aims to make git-submodule foreach a builtin. 'foreach' is ported to the submodule--helper, and submodule--helper is called from git-submodule.sh. Helped-by: Brandon Williams <bmwill@google.com> Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Stefan Beller <sbeller@google.com> Signed-off-by: Prathamesh Chavan <pc44800@gmail.com> Signed-off-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Prathamesh Chavan committed May 10, 2018 at 14:25 UTC fc1b9243cd5d51ae455272f1f953d4002294edee
2 files changed +145 -38
builtin/submodule--helper.c
+144
@@ -439,6 +439,149 @@ static void for_each_listed_submodule(const struct module_list *list,
439 fn(list->entries[i], cb_data);
440 }
441
442 +struct cb_foreach {
443 + int argc;
444 + const char **argv;
445 + const char *prefix;
446 + int quiet;
447 + int recursive;
448 +};
449 +#define CB_FOREACH_INIT { 0 }
450 +
451 +static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
452 + void *cb_data)
453 +{
454 + struct cb_foreach *info = cb_data;
455 + const char *path = list_item->name;
456 + const struct object_id *ce_oid = &list_item->oid;
457 +
458 + const struct submodule *sub;
459 + struct child_process cp = CHILD_PROCESS_INIT;
460 + char *displaypath;
461 +
462 + displaypath = get_submodule_displaypath(path, info->prefix);
463 +
464 + sub = submodule_from_path(the_repository, &null_oid, path);
465 +
466 + if (!sub)
467 + die(_("No url found for submodule path '%s' in .gitmodules"),
468 + displaypath);
469 +
470 + if (!is_submodule_populated_gently(path, NULL))
471 + goto cleanup;
472 +
473 + prepare_submodule_repo_env(&cp.env_array);
474 +
475 + /*
476 + * For the purpose of executing <command> in the submodule,
477 + * separate shell is used for the purpose of running the
478 + * child process.
479 + */
480 + cp.use_shell = 1;
481 + cp.dir = path;
482 +
483 + /*
484 + * NEEDSWORK: the command currently has access to the variables $name,
485 + * $sm_path, $displaypath, $sha1 and $toplevel only when the command
486 + * contains a single argument. This is done for maintaining a faithful
487 + * translation from shell script.
488 + */
489 + if (info->argc == 1) {
490 + char *toplevel = xgetcwd();
491 + struct strbuf sb = STRBUF_INIT;
492 +
493 + argv_array_pushf(&cp.env_array, "name=%s", sub->name);
494 + argv_array_pushf(&cp.env_array, "sm_path=%s", path);
495 + argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath);
496 + argv_array_pushf(&cp.env_array, "sha1=%s",
497 + oid_to_hex(ce_oid));
498 + argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel);
499 +
500 + /*
501 + * Since the path variable was accessible from the script
502 + * before porting, it is also made available after porting.
503 + * The environment variable "PATH" has a very special purpose
504 + * on windows. And since environment variables are
505 + * case-insensitive in windows, it interferes with the
506 + * existing PATH variable. Hence, to avoid that, we expose
507 + * path via the args argv_array and not via env_array.
508 + */
509 + sq_quote_buf(&sb, path);
510 + argv_array_pushf(&cp.args, "path=%s; %s",
511 + sb.buf, info->argv[0]);
512 + strbuf_release(&sb);
513 + free(toplevel);
514 + } else {
515 + argv_array_pushv(&cp.args, info->argv);
516 + }
517 +
518 + if (!info->quiet)
519 + printf(_("Entering '%s'\n"), displaypath);
520 +
521 + if (info->argv[0] && run_command(&cp))
522 + die(_("run_command returned non-zero status for %s\n."),
523 + displaypath);
524 +
525 + if (info->recursive) {
526 + struct child_process cpr = CHILD_PROCESS_INIT;
527 +
528 + cpr.git_cmd = 1;
529 + cpr.dir = path;
530 + prepare_submodule_repo_env(&cpr.env_array);
531 +
532 + argv_array_pushl(&cpr.args, "--super-prefix", NULL);
533 + argv_array_pushf(&cpr.args, "%s/", displaypath);
534 + argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
535 + NULL);
536 +
537 + if (info->quiet)
538 + argv_array_push(&cpr.args, "--quiet");
539 +
540 + argv_array_pushv(&cpr.args, info->argv);
541 +
542 + if (run_command(&cpr))
543 + die(_("run_command returned non-zero status while"
544 + "recursing in the nested submodules of %s\n."),
545 + displaypath);
546 + }
547 +
548 +cleanup:
549 + free(displaypath);
550 +}
551 +
552 +static int module_foreach(int argc, const char **argv, const char *prefix)
553 +{
554 + struct cb_foreach info = CB_FOREACH_INIT;
555 + struct pathspec pathspec;
556 + struct module_list list = MODULE_LIST_INIT;
557 +
558 + struct option module_foreach_options[] = {
559 + OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")),
560 + OPT_BOOL(0, "recursive", &info.recursive,
561 + N_("Recurse into nested submodules")),
562 + OPT_END()
563 + };
564 +
565 + const char *const git_submodule_helper_usage[] = {
566 + N_("git submodule--helper foreach [--quiet] [--recursive] <command>"),
567 + NULL
568 + };
569 +
570 + argc = parse_options(argc, argv, prefix, module_foreach_options,
571 + git_submodule_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
572 +
573 + if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0)
574 + return 1;
575 +
576 + info.argc = argc;
577 + info.argv = argv;
578 + info.prefix = prefix;
579 +
580 + for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info);
581 +
582 + return 0;
583 +}
584 +
585 struct init_cb {
586 const char *prefix;
587 unsigned int flags;
@@ -1841,6 +1984,7 @@ static struct cmd_struct commands[] = {
1984 {"relative-path", resolve_relative_path, 0},
1985 {"resolve-relative-url", resolve_relative_url, 0},
1986 {"resolve-relative-url-test", resolve_relative_url_test, 0},
1987 + {"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
1988 {"init", module_init, SUPPORT_SUPER_PREFIX},
1989 {"status", module_status, SUPPORT_SUPER_PREFIX},
1990 {"print-default-remote", print_default_remote, 0},
git-submodule.sh
+1 -38
@@ -323,44 +323,7 @@ cmd_foreach()
323 shift
324 done
325
326 - toplevel=$(pwd)
327 -
328 - # dup stdin so that it can be restored when running the external
329 - # command in the subshell (and a recursive call to this function)
330 - exec 3<&0
331 -
332 - {
333 - git submodule--helper list --prefix "$wt_prefix" ||
334 - echo "#unmatched" $?
335 - } |
336 - while read -r mode sha1 stage sm_path
337 - do
338 - die_if_unmatched "$mode" "$sha1"
339 - if test -e "$sm_path"/.git
340 - then
341 - displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
342 - say "$(eval_gettext "Entering '\$displaypath'")"
343 - name=$(git submodule--helper name "$sm_path")
344 - (
345 - prefix="$prefix$sm_path/"
346 - sanitize_submodule_env
347 - cd "$sm_path" &&
348 - # we make $path available to scripts ...
349 - path=$sm_path &&
350 - if test $# -eq 1
351 - then
352 - eval "$1"
353 - else
354 - "$@"
355 - fi &&
356 - if test -n "$recursive"
357 - then
358 - cmd_foreach "--recursive" "$@"
359 - fi
360 - ) <&3 3<&- ||
361 - die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")"
362 - fi
363 - done
326 + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} "$@"
327 }
328
329 #