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>maint
							parent
							
								
									5d213e46bb
								
							
						
					
					
						commit
						c51f8f94e5
					
				|  | @ -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}, | ||||
|  |  | |||
							
								
								
									
										100
									
								
								git-submodule.sh
								
								
								
								
							
							
						
						
									
										100
									
								
								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} -- "$@" | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 		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") | ||||
|  | ||||
| 			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'")" | ||||
| 		# 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" | ||||
| 			;; | ||||
| 			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" | ||||
| 		1) | ||||
| 			err="${err};fatal: $out" | ||||
| 			continue | ||||
| 			fi | ||||
| 		fi | ||||
| 			;; | ||||
| 		2|128) | ||||
| 			die_with_status $res "fatal: $out" | ||||
| 			;; | ||||
| 		esac | ||||
|  | ||||
| 		if test -n "$recursive" | ||||
| 		then | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Atharva Raykar
						Atharva Raykar