]> git.ipfire.org Git - thirdparty/git.git/commitdiff
submodule--helper: run update procedures from C
authorAtharva Raykar <raykar.ath@gmail.com>
Tue, 24 Aug 2021 14:06:09 +0000 (19:36 +0530)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Aug 2021 21:03:58 +0000 (14:03 -0700)
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>
builtin/submodule--helper.c
git-submodule.sh

index ef2776a9e4523894c6ff044bffcd9a2c60d2b98f..80619361fc584f02c5e51022b149438d915add50 100644 (file)
@@ -2045,6 +2045,20 @@ struct submodule_update_clone {
        .max_jobs = 1, \
 }
 
+struct update_data {
+       const char *recursive_prefix;
+       const char *sm_path;
+       const char *displaypath;
+       struct object_id oid;
+       struct object_id suboid;
+       struct submodule_update_strategy update_strategy;
+       int depth;
+       unsigned int force: 1;
+       unsigned int quiet: 1;
+       unsigned int nofetch: 1;
+       unsigned int just_cloned: 1;
+};
+#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
                struct strbuf *out, const char *displaypath)
@@ -2298,6 +2312,181 @@ static int git_update_clone_config(const char *var, const char *value,
        return 0;
 }
 
+static int is_tip_reachable(const char *path, struct object_id *oid)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+       struct strbuf rev = STRBUF_INIT;
+       char *hex = oid_to_hex(oid);
+
+       cp.git_cmd = 1;
+       cp.dir = xstrdup(path);
+       cp.no_stderr = 1;
+       strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL);
+
+       prepare_submodule_repo_env(&cp.env_array);
+
+       if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len)
+               return 0;
+
+       return 1;
+}
+
+static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+
+       prepare_submodule_repo_env(&cp.env_array);
+       cp.git_cmd = 1;
+       cp.dir = xstrdup(module_path);
+
+       strvec_push(&cp.args, "fetch");
+       if (quiet)
+               strvec_push(&cp.args, "--quiet");
+       if (depth)
+               strvec_pushf(&cp.args, "--depth=%d", depth);
+       if (oid) {
+               char *hex = oid_to_hex(oid);
+               char *remote = get_default_remote();
+               strvec_pushl(&cp.args, remote, hex, NULL);
+       }
+
+       return run_command(&cp);
+}
+
+static int run_update_command(struct update_data *ud, int subforce)
+{
+       struct strvec args = STRVEC_INIT;
+       struct strvec child_env = STRVEC_INIT;
+       char *oid = oid_to_hex(&ud->oid);
+       int must_die_on_failure = 0;
+       int git_cmd;
+
+       switch (ud->update_strategy.type) {
+       case SM_UPDATE_CHECKOUT:
+               git_cmd = 1;
+               strvec_pushl(&args, "checkout", "-q", NULL);
+               if (subforce)
+                       strvec_push(&args, "-f");
+               break;
+       case SM_UPDATE_REBASE:
+               git_cmd = 1;
+               strvec_push(&args, "rebase");
+               if (ud->quiet)
+                       strvec_push(&args, "--quiet");
+               must_die_on_failure = 1;
+               break;
+       case SM_UPDATE_MERGE:
+               git_cmd = 1;
+               strvec_push(&args, "merge");
+               if (ud->quiet)
+                       strvec_push(&args, "--quiet");
+               must_die_on_failure = 1;
+               break;
+       case SM_UPDATE_COMMAND:
+               git_cmd = 0;
+               strvec_push(&args, ud->update_strategy.command);
+               must_die_on_failure = 1;
+               break;
+       default:
+               BUG("unexpected update strategy type: %s",
+                   submodule_strategy_to_string(&ud->update_strategy));
+       }
+       strvec_push(&args, oid);
+
+       prepare_submodule_repo_env(&child_env);
+       if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
+                                    ud->sm_path, child_env.v)) {
+               switch (ud->update_strategy.type) {
+               case SM_UPDATE_CHECKOUT:
+                       printf(_("Unable to checkout '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_REBASE:
+                       printf(_("Unable to rebase '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_MERGE:
+                       printf(_("Unable to merge '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_COMMAND:
+                       printf(_("Execution of '%s %s' failed in submodule path '%s'"),
+                              ud->update_strategy.command, oid, ud->displaypath);
+                       break;
+               default:
+                       BUG("unexpected update strategy type: %s",
+                           submodule_strategy_to_string(&ud->update_strategy));
+               }
+               /*
+                * NEEDSWORK: We are currently printing to stdout with error
+                * return so that the shell caller handles the error output
+                * properly. Once we start handling the error messages within
+                * C, we should use die() instead.
+                */
+               if (must_die_on_failure)
+                       return 2;
+               /*
+                * This signifies to the caller in shell that the command
+                * failed without dying
+                */
+               return 1;
+       }
+
+       switch (ud->update_strategy.type) {
+       case SM_UPDATE_CHECKOUT:
+               printf(_("Submodule path '%s': checked out '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_REBASE:
+               printf(_("Submodule path '%s': rebased into '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_MERGE:
+               printf(_("Submodule path '%s': merged in '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_COMMAND:
+               printf(_("Submodule path '%s': '%s %s'\n"),
+                      ud->displaypath, ud->update_strategy.command, oid);
+               break;
+       default:
+               BUG("unexpected update strategy type: %s",
+                   submodule_strategy_to_string(&ud->update_strategy));
+       }
+
+       return 0;
+}
+
+static int do_run_update_procedure(struct update_data *ud)
+{
+       int subforce = is_null_oid(&ud->suboid) || ud->force;
+
+       if (!ud->nofetch) {
+               /*
+                * Run fetch only if `oid` isn't present or it
+                * is not reachable from a ref.
+                */
+               if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+                   fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, NULL) &&
+                   !ud->quiet)
+                       fprintf_ln(stderr,
+                                  _("Unable to fetch in submodule path '%s'; "
+                                    "trying to directly fetch %s:"),
+                                  ud->displaypath, oid_to_hex(&ud->oid));
+               /*
+                * Now we tried the usual fetch, but `oid` may
+                * not be reachable from any of the refs.
+                */
+               if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+                   fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid))
+                       die(_("Fetched in submodule path '%s', but it did not "
+                             "contain %s. Direct fetching of that commit failed."),
+                           ud->displaypath, oid_to_hex(&ud->oid));
+       }
+
+       return run_update_command(ud, subforce);
+}
+
 static void update_submodule(struct update_clone_data *ucd)
 {
        fprintf(stdout, "dummy %s %d\t%s\n",
@@ -2395,6 +2584,73 @@ static int update_clone(int argc, const char **argv, const char *prefix)
        return update_submodules(&suc);
 }
 
+static int run_update_procedure(int argc, const char **argv, const char *prefix)
+{
+       int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
+       char *prefixed_path, *update = NULL;
+       struct update_data update_data = UPDATE_DATA_INIT;
+
+       struct option options[] = {
+               OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
+               OPT__FORCE(&force, N_("force checkout updates"), 0),
+               OPT_BOOL('N', "no-fetch", &nofetch,
+                        N_("don't fetch new objects from the remote site")),
+               OPT_BOOL(0, "just-cloned", &just_cloned,
+                        N_("overrides update mode in case the repository is a fresh clone")),
+               OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("path into the working tree")),
+               OPT_STRING(0, "update", &update,
+                          N_("string"),
+                          N_("rebase, merge, checkout or none")),
+               OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+                          N_("path into the working tree, across nested "
+                             "submodule boundaries")),
+               OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+                              N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
+                              parse_opt_object_id),
+               OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+                              N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
+                              parse_opt_object_id),
+               OPT_END()
+       };
+
+       const char *const usage[] = {
+               N_("git submodule--helper run-update-procedure [<options>] <path>"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+       if (argc != 1)
+               usage_with_options(usage, options);
+
+       update_data.force = !!force;
+       update_data.quiet = !!quiet;
+       update_data.nofetch = !!nofetch;
+       update_data.just_cloned = !!just_cloned;
+       update_data.sm_path = argv[0];
+
+       if (update_data.recursive_prefix)
+               prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+       else
+               prefixed_path = xstrdup(update_data.sm_path);
+
+       update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+
+       determine_submodule_update_strategy(the_repository, update_data.just_cloned,
+                                           update_data.sm_path, update,
+                                           &update_data.update_strategy);
+
+       free(prefixed_path);
+
+       if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
+               return do_run_update_procedure(&update_data);
+
+       return 3;
+}
+
 static int resolve_relative_path(int argc, const char **argv, const char *prefix)
 {
        struct strbuf sb = STRBUF_INIT;
@@ -2951,6 +3207,7 @@ static struct cmd_struct commands[] = {
        {"add-clone", add_clone, 0},
        {"update-module-mode", module_update_module_mode, 0},
        {"update-clone", update_clone, 0},
+       {"run-update-procedure", run_update_procedure, 0},
        {"ensure-core-worktree", ensure_core_worktree, 0},
        {"relative-path", resolve_relative_path, 0},
        {"resolve-relative-url", resolve_relative_url, 0},
index dbd2ec205037e062b6fb4ae52205bbf84e023001..f703cddce838293258f78e1dcb645f599584e01e 100755 (executable)
@@ -369,13 +369,6 @@ cmd_deinit()
        git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-is_tip_reachable () (
-       sanitize_submodule_env &&
-       cd "$1" &&
-       rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
-       test -z "$rev"
-)
-
 # usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
 # Because arguments are positional, use an empty string to omit <depth>
 # but include <sha1>.
@@ -519,14 +512,13 @@ cmd_update()
 
                git submodule--helper ensure-core-worktree "$sm_path" || exit 1
 
-               update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
-
                displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
                if test $just_cloned -eq 1
                then
                        subsha1=
                else
+                       just_cloned=
                        subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
                        die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
@@ -547,70 +539,38 @@ cmd_update()
                        die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
                fi
 
-               if test "$subsha1" != "$sha1" || test -n "$force"
-               then
-                       subforce=$force
-                       # If we don't already have a -f flag and the submodule has never been checked out
-                       if test -z "$subsha1" && test -z "$force"
-                       then
-                               subforce="-f"
-                       fi
-
-                       if test -z "$nofetch"
-                       then
-                               # Run fetch only if $sha1 isn't present or it
-                               # is not reachable from a ref.
-                               is_tip_reachable "$sm_path" "$sha1" ||
-                               fetch_in_submodule "$sm_path" $depth ||
-                               say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'; trying to directly fetch \$sha1:")"
-
-                               # Now we tried the usual fetch, but $sha1 may
-                               # not be reachable from any of the refs
-                               is_tip_reachable "$sm_path" "$sha1" ||
-                               fetch_in_submodule "$sm_path" "$depth" "$sha1" ||
-                               die "fatal: $(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
-                       fi
-
-                       must_die_on_failure=
-                       case "$update_module" in
-                       checkout)
-                               command="git checkout $subforce -q"
-                               die_msg="fatal: $(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
-                               ;;
-                       rebase)
-                               command="git rebase ${GIT_QUIET:+--quiet}"
-                               die_msg="fatal: $(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       merge)
-                               command="git merge ${GIT_QUIET:+--quiet}"
-                               die_msg="fatal: $(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       !*)
-                               command="${update_module#!}"
-                               die_msg="fatal: $(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       *)
-                               die "fatal: $(eval_gettext "Invalid update mode '$update_module' for submodule path '$path'")"
-                       esac
-
-                       if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
-                       then
-                               say "$say_msg"
-                       elif test -n "$must_die_on_failure"
-                       then
-                               die_with_status 2 "$die_msg"
-                       else
-                               err="${err};$die_msg"
-                               continue
-                       fi
-               fi
+               out=$(git submodule--helper run-update-procedure \
+                         ${wt_prefix:+--prefix "$wt_prefix"} \
+                         ${GIT_QUIET:+--quiet} \
+                         ${force:+--force} \
+                         ${just_cloned:+--just-cloned} \
+                         ${nofetch:+--no-fetch} \
+                         ${depth:+"$depth"} \
+                         ${update:+--update "$update"} \
+                         ${prefix:+--recursive-prefix "$prefix"} \
+                         ${sha1:+--oid "$sha1"} \
+                         ${subsha1:+--suboid "$subsha1"} \
+                         "--" \
+                         "$sm_path")
+
+               # exit codes for run-update-procedure:
+               # 0: update was successful, say command output
+               # 1: update procedure failed, but should not die
+               # 2 or 128: subcommand died during execution
+               # 3: no update procedure was run
+               res="$?"
+               case $res in
+               0)
+                       say "$out"
+                       ;;
+               1)
+                       err="${err};fatal: $out"
+                       continue
+                       ;;
+               2|128)
+                       die_with_status $res "fatal: $out"
+                       ;;
+               esac
 
                if test -n "$recursive"
                then