submodule--helper: run update procedures from C

Add a new submodule--helper subcommand `run-update-procedure` that runs the update procedure if the SHA1 of the submodule does not match what the superproject expects. This is an intermediate change that works towards total conversion of `submodule update` from shell to C. Specific error codes are returned so that the shell script calling the subcommand can take a decision on the control flow, and preserve the error messages across subsequent recursive calls of `cmd_update`. This change is more focused on doing a faithful conversion, so for now we are not too concerned with trying to reduce subprocess spawns. Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Shourya Shukla <periperidip@gmail.com> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Atharva Raykar committed Aug 24, 2021 at 19:36 UTC c51f8f94e5b3d6a8d190d9901ea0c5b83e30c3aa
2 files changed +290 -73
builtin/submodule--helper.c
+257
@@ -2045,6 +2045,20 @@ struct submodule_update_clone {
2045 .max_jobs = 1, \
2046 }
2047
2048 +struct update_data {
2049 + const char *recursive_prefix;
2050 + const char *sm_path;
2051 + const char *displaypath;
2052 + struct object_id oid;
2053 + struct object_id suboid;
2054 + struct submodule_update_strategy update_strategy;
2055 + int depth;
2056 + unsigned int force: 1;
2057 + unsigned int quiet: 1;
2058 + unsigned int nofetch: 1;
2059 + unsigned int just_cloned: 1;
2060 +};
2061 +#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
2062
2063 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
2064 struct strbuf *out, const char *displaypath)
@@ -2298,6 +2312,181 @@ static int git_update_clone_config(const char *var, const char *value,
2312 return 0;
2313 }
2314
2315 +static int is_tip_reachable(const char *path, struct object_id *oid)
2316 +{
2317 + struct child_process cp = CHILD_PROCESS_INIT;
2318 + struct strbuf rev = STRBUF_INIT;
2319 + char *hex = oid_to_hex(oid);
2320 +
2321 + cp.git_cmd = 1;
2322 + cp.dir = xstrdup(path);
2323 + cp.no_stderr = 1;
2324 + strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL);
2325 +
2326 + prepare_submodule_repo_env(&cp.env_array);
2327 +
2328 + if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len)
2329 + return 0;
2330 +
2331 + return 1;
2332 +}
2333 +
2334 +static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid)
2335 +{
2336 + struct child_process cp = CHILD_PROCESS_INIT;
2337 +
2338 + prepare_submodule_repo_env(&cp.env_array);
2339 + cp.git_cmd = 1;
2340 + cp.dir = xstrdup(module_path);
2341 +
2342 + strvec_push(&cp.args, "fetch");
2343 + if (quiet)
2344 + strvec_push(&cp.args, "--quiet");
2345 + if (depth)
2346 + strvec_pushf(&cp.args, "--depth=%d", depth);
2347 + if (oid) {
2348 + char *hex = oid_to_hex(oid);
2349 + char *remote = get_default_remote();
2350 + strvec_pushl(&cp.args, remote, hex, NULL);
2351 + }
2352 +
2353 + return run_command(&cp);
2354 +}
2355 +
2356 +static int run_update_command(struct update_data *ud, int subforce)
2357 +{
2358 + struct strvec args = STRVEC_INIT;
2359 + struct strvec child_env = STRVEC_INIT;
2360 + char *oid = oid_to_hex(&ud->oid);
2361 + int must_die_on_failure = 0;
2362 + int git_cmd;
2363 +
2364 + switch (ud->update_strategy.type) {
2365 + case SM_UPDATE_CHECKOUT:
2366 + git_cmd = 1;
2367 + strvec_pushl(&args, "checkout", "-q", NULL);
2368 + if (subforce)
2369 + strvec_push(&args, "-f");
2370 + break;
2371 + case SM_UPDATE_REBASE:
2372 + git_cmd = 1;
2373 + strvec_push(&args, "rebase");
2374 + if (ud->quiet)
2375 + strvec_push(&args, "--quiet");
2376 + must_die_on_failure = 1;
2377 + break;
2378 + case SM_UPDATE_MERGE:
2379 + git_cmd = 1;
2380 + strvec_push(&args, "merge");
2381 + if (ud->quiet)
2382 + strvec_push(&args, "--quiet");
2383 + must_die_on_failure = 1;
2384 + break;
2385 + case SM_UPDATE_COMMAND:
2386 + git_cmd = 0;
2387 + strvec_push(&args, ud->update_strategy.command);
2388 + must_die_on_failure = 1;
2389 + break;
2390 + default:
2391 + BUG("unexpected update strategy type: %s",
2392 + submodule_strategy_to_string(&ud->update_strategy));
2393 + }
2394 + strvec_push(&args, oid);
2395 +
2396 + prepare_submodule_repo_env(&child_env);
2397 + if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
2398 + ud->sm_path, child_env.v)) {
2399 + switch (ud->update_strategy.type) {
2400 + case SM_UPDATE_CHECKOUT:
2401 + printf(_("Unable to checkout '%s' in submodule path '%s'"),
2402 + oid, ud->displaypath);
2403 + break;
2404 + case SM_UPDATE_REBASE:
2405 + printf(_("Unable to rebase '%s' in submodule path '%s'"),
2406 + oid, ud->displaypath);
2407 + break;
2408 + case SM_UPDATE_MERGE:
2409 + printf(_("Unable to merge '%s' in submodule path '%s'"),
2410 + oid, ud->displaypath);
2411 + break;
2412 + case SM_UPDATE_COMMAND:
2413 + printf(_("Execution of '%s %s' failed in submodule path '%s'"),
2414 + ud->update_strategy.command, oid, ud->displaypath);
2415 + break;
2416 + default:
2417 + BUG("unexpected update strategy type: %s",
2418 + submodule_strategy_to_string(&ud->update_strategy));
2419 + }
2420 + /*
2421 + * NEEDSWORK: We are currently printing to stdout with error
2422 + * return so that the shell caller handles the error output
2423 + * properly. Once we start handling the error messages within
2424 + * C, we should use die() instead.
2425 + */
2426 + if (must_die_on_failure)
2427 + return 2;
2428 + /*
2429 + * This signifies to the caller in shell that the command
2430 + * failed without dying
2431 + */
2432 + return 1;
2433 + }
2434 +
2435 + switch (ud->update_strategy.type) {
2436 + case SM_UPDATE_CHECKOUT:
2437 + printf(_("Submodule path '%s': checked out '%s'\n"),
2438 + ud->displaypath, oid);
2439 + break;
2440 + case SM_UPDATE_REBASE:
2441 + printf(_("Submodule path '%s': rebased into '%s'\n"),
2442 + ud->displaypath, oid);
2443 + break;
2444 + case SM_UPDATE_MERGE:
2445 + printf(_("Submodule path '%s': merged in '%s'\n"),
2446 + ud->displaypath, oid);
2447 + break;
2448 + case SM_UPDATE_COMMAND:
2449 + printf(_("Submodule path '%s': '%s %s'\n"),
2450 + ud->displaypath, ud->update_strategy.command, oid);
2451 + break;
2452 + default:
2453 + BUG("unexpected update strategy type: %s",
2454 + submodule_strategy_to_string(&ud->update_strategy));
2455 + }
2456 +
2457 + return 0;
2458 +}
2459 +
2460 +static int do_run_update_procedure(struct update_data *ud)
2461 +{
2462 + int subforce = is_null_oid(&ud->suboid) || ud->force;
2463 +
2464 + if (!ud->nofetch) {
2465 + /*
2466 + * Run fetch only if `oid` isn't present or it
2467 + * is not reachable from a ref.
2468 + */
2469 + if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
2470 + fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, NULL) &&
2471 + !ud->quiet)
2472 + fprintf_ln(stderr,
2473 + _("Unable to fetch in submodule path '%s'; "
2474 + "trying to directly fetch %s:"),
2475 + ud->displaypath, oid_to_hex(&ud->oid));
2476 + /*
2477 + * Now we tried the usual fetch, but `oid` may
2478 + * not be reachable from any of the refs.
2479 + */
2480 + if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
2481 + fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid))
2482 + die(_("Fetched in submodule path '%s', but it did not "
2483 + "contain %s. Direct fetching of that commit failed."),
2484 + ud->displaypath, oid_to_hex(&ud->oid));
2485 + }
2486 +
2487 + return run_update_command(ud, subforce);
2488 +}
2489 +
2490 static void update_submodule(struct update_clone_data *ucd)
2491 {
2492 fprintf(stdout, "dummy %s %d\t%s\n",
@@ -2395,6 +2584,73 @@ static int update_clone(int argc, const char **argv, const char *prefix)
2584 return update_submodules(&suc);
2585 }
2586
2587 +static int run_update_procedure(int argc, const char **argv, const char *prefix)
2588 +{
2589 + int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
2590 + char *prefixed_path, *update = NULL;
2591 + struct update_data update_data = UPDATE_DATA_INIT;
2592 +
2593 + struct option options[] = {
2594 + OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
2595 + OPT__FORCE(&force, N_("force checkout updates"), 0),
2596 + OPT_BOOL('N', "no-fetch", &nofetch,
2597 + N_("don't fetch new objects from the remote site")),
2598 + OPT_BOOL(0, "just-cloned", &just_cloned,
2599 + N_("overrides update mode in case the repository is a fresh clone")),
2600 + OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
2601 + OPT_STRING(0, "prefix", &prefix,
2602 + N_("path"),
2603 + N_("path into the working tree")),
2604 + OPT_STRING(0, "update", &update,
2605 + N_("string"),
2606 + N_("rebase, merge, checkout or none")),
2607 + OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
2608 + N_("path into the working tree, across nested "
2609 + "submodule boundaries")),
2610 + OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
2611 + N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
2612 + parse_opt_object_id),
2613 + OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
2614 + N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
2615 + parse_opt_object_id),
2616 + OPT_END()
2617 + };
2618 +
2619 + const char *const usage[] = {
2620 + N_("git submodule--helper run-update-procedure [<options>] <path>"),
2621 + NULL
2622 + };
2623 +
2624 + argc = parse_options(argc, argv, prefix, options, usage, 0);
2625 +
2626 + if (argc != 1)
2627 + usage_with_options(usage, options);
2628 +
2629 + update_data.force = !!force;
2630 + update_data.quiet = !!quiet;
2631 + update_data.nofetch = !!nofetch;
2632 + update_data.just_cloned = !!just_cloned;
2633 + update_data.sm_path = argv[0];
2634 +
2635 + if (update_data.recursive_prefix)
2636 + prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
2637 + else
2638 + prefixed_path = xstrdup(update_data.sm_path);
2639 +
2640 + update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
2641 +
2642 + determine_submodule_update_strategy(the_repository, update_data.just_cloned,
2643 + update_data.sm_path, update,
2644 + &update_data.update_strategy);
2645 +
2646 + free(prefixed_path);
2647 +
2648 + if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
2649 + return do_run_update_procedure(&update_data);
2650 +
2651 + return 3;
2652 +}
2653 +
2654 static int resolve_relative_path(int argc, const char **argv, const char *prefix)
2655 {
2656 struct strbuf sb = STRBUF_INIT;
@@ -2951,6 +3207,7 @@ static struct cmd_struct commands[] = {
3207 {"add-clone", add_clone, 0},
3208 {"update-module-mode", module_update_module_mode, 0},
3209 {"update-clone", update_clone, 0},
3210 + {"run-update-procedure", run_update_procedure, 0},
3211 {"ensure-core-worktree", ensure_core_worktree, 0},
3212 {"relative-path", resolve_relative_path, 0},
3213 {"resolve-relative-url", resolve_relative_url, 0},
git-submodule.sh
+33 -73
@@ -369,13 +369,6 @@ cmd_deinit()
369 git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
370 }
371
372 -is_tip_reachable () (
373 - sanitize_submodule_env &&
374 - cd "$1" &&
375 - rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
376 - test -z "$rev"
377 -)
378 -
372 # usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
373 # Because arguments are positional, use an empty string to omit <depth>
374 # but include <sha1>.
@@ -519,14 +512,13 @@ cmd_update()
512
513 git submodule--helper ensure-core-worktree "$sm_path" || exit 1
514
522 - update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
523 -
515 displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
516
517 if test $just_cloned -eq 1
518 then
519 subsha1=
520 else
521 + just_cloned=
522 subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
523 git rev-parse --verify HEAD) ||
524 die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
@@ -547,70 +539,38 @@ cmd_update()
539 die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
540 fi
541
550 - if test "$subsha1" != "$sha1" || test -n "$force"
551 - then
552 - subforce=$force
553 - # If we don't already have a -f flag and the submodule has never been checked out
554 - if test -z "$subsha1" && test -z "$force"
555 - then
556 - subforce="-f"
557 - fi
558 -
559 - if test -z "$nofetch"
560 - then
561 - # Run fetch only if $sha1 isn't present or it
562 - # is not reachable from a ref.
563 - is_tip_reachable "$sm_path" "$sha1" ||
564 - fetch_in_submodule "$sm_path" $depth ||
565 - say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'; trying to directly fetch \$sha1:")"
566 -
567 - # Now we tried the usual fetch, but $sha1 may
568 - # not be reachable from any of the refs
569 - is_tip_reachable "$sm_path" "$sha1" ||
570 - fetch_in_submodule "$sm_path" "$depth" "$sha1" ||
571 - die "fatal: $(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
572 - fi
573 -
574 - must_die_on_failure=
575 - case "$update_module" in
576 - checkout)
577 - command="git checkout $subforce -q"
578 - die_msg="fatal: $(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
579 - say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
580 - ;;
581 - rebase)
582 - command="git rebase ${GIT_QUIET:+--quiet}"
583 - die_msg="fatal: $(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
584 - say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
585 - must_die_on_failure=yes
586 - ;;
587 - merge)
588 - command="git merge ${GIT_QUIET:+--quiet}"
589 - die_msg="fatal: $(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
590 - say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
591 - must_die_on_failure=yes
592 - ;;
593 - !*)
594 - command="${update_module#!}"
595 - die_msg="fatal: $(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")"
596 - say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")"
597 - must_die_on_failure=yes
598 - ;;
599 - *)
600 - die "fatal: $(eval_gettext "Invalid update mode '$update_module' for submodule path '$path'")"
601 - esac
602 -
603 - if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
604 - then
605 - say "$say_msg"
606 - elif test -n "$must_die_on_failure"
607 - then
608 - die_with_status 2 "$die_msg"
609 - else
610 - err="${err};$die_msg"
611 - continue
612 - fi
613 - fi
542 + out=$(git submodule--helper run-update-procedure \
543 + ${wt_prefix:+--prefix "$wt_prefix"} \
544 + ${GIT_QUIET:+--quiet} \
545 + ${force:+--force} \
546 + ${just_cloned:+--just-cloned} \
547 + ${nofetch:+--no-fetch} \
548 + ${depth:+"$depth"} \
549 + ${update:+--update "$update"} \
550 + ${prefix:+--recursive-prefix "$prefix"} \
551 + ${sha1:+--oid "$sha1"} \
552 + ${subsha1:+--suboid "$subsha1"} \
553 + "--" \
554 + "$sm_path")
555 +
556 + # exit codes for run-update-procedure:
557 + # 0: update was successful, say command output
558 + # 1: update procedure failed, but should not die
559 + # 2 or 128: subcommand died during execution
560 + # 3: no update procedure was run
561 + res="$?"
562 + case $res in
563 + 0)
564 + say "$out"
565 + ;;
566 + 1)
567 + err="${err};fatal: $out"
568 + continue
569 + ;;
570 + 2|128)
571 + die_with_status $res "fatal: $out"
572 + ;;
573 + esac
574
575 if test -n "$recursive"
576 then