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>
This commit is contained in:
parent
5d213e46bb
commit
c51f8f94e5
@ -2045,6 +2045,20 @@ struct submodule_update_clone {
|
|||||||
.max_jobs = 1, \
|
.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,
|
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
|
||||||
struct strbuf *out, const char *displaypath)
|
struct strbuf *out, const char *displaypath)
|
||||||
@ -2298,6 +2312,181 @@ static int git_update_clone_config(const char *var, const char *value,
|
|||||||
return 0;
|
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)
|
static void update_submodule(struct update_clone_data *ucd)
|
||||||
{
|
{
|
||||||
fprintf(stdout, "dummy %s %d\t%s\n",
|
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);
|
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)
|
static int resolve_relative_path(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
@ -2951,6 +3207,7 @@ static struct cmd_struct commands[] = {
|
|||||||
{"add-clone", add_clone, 0},
|
{"add-clone", add_clone, 0},
|
||||||
{"update-module-mode", module_update_module_mode, 0},
|
{"update-module-mode", module_update_module_mode, 0},
|
||||||
{"update-clone", update_clone, 0},
|
{"update-clone", update_clone, 0},
|
||||||
|
{"run-update-procedure", run_update_procedure, 0},
|
||||||
{"ensure-core-worktree", ensure_core_worktree, 0},
|
{"ensure-core-worktree", ensure_core_worktree, 0},
|
||||||
{"relative-path", resolve_relative_path, 0},
|
{"relative-path", resolve_relative_path, 0},
|
||||||
{"resolve-relative-url", resolve_relative_url, 0},
|
{"resolve-relative-url", resolve_relative_url, 0},
|
||||||
|
104
git-submodule.sh
104
git-submodule.sh
@ -369,13 +369,6 @@ cmd_deinit()
|
|||||||
git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
|
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>]
|
# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
|
||||||
# Because arguments are positional, use an empty string to omit <depth>
|
# Because arguments are positional, use an empty string to omit <depth>
|
||||||
# but include <sha1>.
|
# but include <sha1>.
|
||||||
@ -519,14 +512,13 @@ cmd_update()
|
|||||||
|
|
||||||
git submodule--helper ensure-core-worktree "$sm_path" || exit 1
|
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")
|
displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
|
||||||
|
|
||||||
if test $just_cloned -eq 1
|
if test $just_cloned -eq 1
|
||||||
then
|
then
|
||||||
subsha1=
|
subsha1=
|
||||||
else
|
else
|
||||||
|
just_cloned=
|
||||||
subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
|
subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
|
||||||
git rev-parse --verify HEAD) ||
|
git rev-parse --verify HEAD) ||
|
||||||
die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
|
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'")"
|
die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$subsha1" != "$sha1" || test -n "$force"
|
out=$(git submodule--helper run-update-procedure \
|
||||||
then
|
${wt_prefix:+--prefix "$wt_prefix"} \
|
||||||
subforce=$force
|
${GIT_QUIET:+--quiet} \
|
||||||
# If we don't already have a -f flag and the submodule has never been checked out
|
${force:+--force} \
|
||||||
if test -z "$subsha1" && test -z "$force"
|
${just_cloned:+--just-cloned} \
|
||||||
then
|
${nofetch:+--no-fetch} \
|
||||||
subforce="-f"
|
${depth:+"$depth"} \
|
||||||
fi
|
${update:+--update "$update"} \
|
||||||
|
${prefix:+--recursive-prefix "$prefix"} \
|
||||||
|
${sha1:+--oid "$sha1"} \
|
||||||
|
${subsha1:+--suboid "$subsha1"} \
|
||||||
|
"--" \
|
||||||
|
"$sm_path")
|
||||||
|
|
||||||
if test -z "$nofetch"
|
# exit codes for run-update-procedure:
|
||||||
then
|
# 0: update was successful, say command output
|
||||||
# Run fetch only if $sha1 isn't present or it
|
# 1: update procedure failed, but should not die
|
||||||
# is not reachable from a ref.
|
# 2 or 128: subcommand died during execution
|
||||||
is_tip_reachable "$sm_path" "$sha1" ||
|
# 3: no update procedure was run
|
||||||
fetch_in_submodule "$sm_path" $depth ||
|
res="$?"
|
||||||
say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'; trying to directly fetch \$sha1:")"
|
case $res in
|
||||||
|
0)
|
||||||
# Now we tried the usual fetch, but $sha1 may
|
say "$out"
|
||||||
# not be reachable from any of the refs
|
;;
|
||||||
is_tip_reachable "$sm_path" "$sha1" ||
|
1)
|
||||||
fetch_in_submodule "$sm_path" "$depth" "$sha1" ||
|
err="${err};fatal: $out"
|
||||||
die "fatal: $(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
|
continue
|
||||||
fi
|
;;
|
||||||
|
2|128)
|
||||||
must_die_on_failure=
|
die_with_status $res "fatal: $out"
|
||||||
case "$update_module" in
|
;;
|
||||||
checkout)
|
esac
|
||||||
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
|
|
||||||
|
|
||||||
if test -n "$recursive"
|
if test -n "$recursive"
|
||||||
then
|
then
|
||||||
|
Loading…
Reference in New Issue
Block a user