git submodule update: have a dedicated helper for cloning
This introduces a new helper function in git submodule--helper which takes care of cloning all submodules, which we want to parallelize eventually. Some tests (such as empty URL, update_mode=none) are required in the helper to make the decision for cloning. These checks have been moved into the C function as well (no need to repeat them in the shell script). Reviewed-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									aa71049485
								
							
						
					
					
						commit
						48308681b0
					
				|  | @ -255,6 +255,251 @@ static int module_clone(int argc, const char **argv, const char *prefix) | |||
| 	return 0; | ||||
| } | ||||
|  | ||||
| struct submodule_update_clone { | ||||
| 	/* index into 'list', the list of submodules to look into for cloning */ | ||||
| 	int current; | ||||
| 	struct module_list list; | ||||
| 	unsigned warn_if_uninitialized : 1; | ||||
|  | ||||
| 	/* update parameter passed via commandline */ | ||||
| 	struct submodule_update_strategy update; | ||||
|  | ||||
| 	/* configuration parameters which are passed on to the children */ | ||||
| 	int quiet; | ||||
| 	const char *reference; | ||||
| 	const char *depth; | ||||
| 	const char *recursive_prefix; | ||||
| 	const char *prefix; | ||||
|  | ||||
| 	/* Machine-readable status lines to be consumed by git-submodule.sh */ | ||||
| 	struct string_list projectlines; | ||||
|  | ||||
| 	/* If we want to stop as fast as possible and return an error */ | ||||
| 	unsigned quickstop : 1; | ||||
| }; | ||||
| #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ | ||||
| 	SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \ | ||||
| 	STRING_LIST_INIT_DUP, 0} | ||||
|  | ||||
| /** | ||||
|  * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to | ||||
|  * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise. | ||||
|  */ | ||||
| static int prepare_to_clone_next_submodule(const struct cache_entry *ce, | ||||
| 					   struct child_process *child, | ||||
| 					   struct submodule_update_clone *suc, | ||||
| 					   struct strbuf *out) | ||||
| { | ||||
| 	const struct submodule *sub = NULL; | ||||
| 	struct strbuf displaypath_sb = STRBUF_INIT; | ||||
| 	struct strbuf sb = STRBUF_INIT; | ||||
| 	const char *displaypath = NULL; | ||||
| 	char *url = NULL; | ||||
| 	int needs_cloning = 0; | ||||
|  | ||||
| 	if (ce_stage(ce)) { | ||||
| 		if (suc->recursive_prefix) | ||||
| 			strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name); | ||||
| 		else | ||||
| 			strbuf_addf(&sb, "%s", ce->name); | ||||
| 		strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); | ||||
| 		strbuf_addch(out, '\n'); | ||||
| 		goto cleanup; | ||||
| 	} | ||||
|  | ||||
| 	sub = submodule_from_path(null_sha1, ce->name); | ||||
|  | ||||
| 	if (suc->recursive_prefix) | ||||
| 		displaypath = relative_path(suc->recursive_prefix, | ||||
| 					    ce->name, &displaypath_sb); | ||||
| 	else | ||||
| 		displaypath = ce->name; | ||||
|  | ||||
| 	if (suc->update.type == SM_UPDATE_NONE | ||||
| 	    || (suc->update.type == SM_UPDATE_UNSPECIFIED | ||||
| 		&& sub->update_strategy.type == SM_UPDATE_NONE)) { | ||||
| 		strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); | ||||
| 		strbuf_addch(out, '\n'); | ||||
| 		goto cleanup; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Looking up the url in .git/config. | ||||
| 	 * We must not fall back to .gitmodules as we only want | ||||
| 	 * to process configured submodules. | ||||
| 	 */ | ||||
| 	strbuf_reset(&sb); | ||||
| 	strbuf_addf(&sb, "submodule.%s.url", sub->name); | ||||
| 	git_config_get_string(sb.buf, &url); | ||||
| 	if (!url) { | ||||
| 		/* | ||||
| 		 * Only mention uninitialized submodules when their | ||||
| 		 * path have been specified | ||||
| 		 */ | ||||
| 		if (suc->warn_if_uninitialized) { | ||||
| 			strbuf_addf(out, | ||||
| 				_("Submodule path '%s' not initialized"), | ||||
| 				displaypath); | ||||
| 			strbuf_addch(out, '\n'); | ||||
| 			strbuf_addstr(out, | ||||
| 				_("Maybe you want to use 'update --init'?")); | ||||
| 			strbuf_addch(out, '\n'); | ||||
| 		} | ||||
| 		goto cleanup; | ||||
| 	} | ||||
|  | ||||
| 	strbuf_reset(&sb); | ||||
| 	strbuf_addf(&sb, "%s/.git", ce->name); | ||||
| 	needs_cloning = !file_exists(sb.buf); | ||||
|  | ||||
| 	strbuf_reset(&sb); | ||||
| 	strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, | ||||
| 			sha1_to_hex(ce->sha1), ce_stage(ce), | ||||
| 			needs_cloning, ce->name); | ||||
| 	string_list_append(&suc->projectlines, sb.buf); | ||||
|  | ||||
| 	if (!needs_cloning) | ||||
| 		goto cleanup; | ||||
|  | ||||
| 	child->git_cmd = 1; | ||||
| 	child->no_stdin = 1; | ||||
| 	child->stdout_to_stderr = 1; | ||||
| 	child->err = -1; | ||||
| 	argv_array_push(&child->args, "submodule--helper"); | ||||
| 	argv_array_push(&child->args, "clone"); | ||||
| 	if (suc->quiet) | ||||
| 		argv_array_push(&child->args, "--quiet"); | ||||
| 	if (suc->prefix) | ||||
| 		argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); | ||||
| 	argv_array_pushl(&child->args, "--path", sub->path, NULL); | ||||
| 	argv_array_pushl(&child->args, "--name", sub->name, NULL); | ||||
| 	argv_array_pushl(&child->args, "--url", url, NULL); | ||||
| 	if (suc->reference) | ||||
| 		argv_array_push(&child->args, suc->reference); | ||||
| 	if (suc->depth) | ||||
| 		argv_array_push(&child->args, suc->depth); | ||||
|  | ||||
| cleanup: | ||||
| 	free(url); | ||||
| 	strbuf_reset(&displaypath_sb); | ||||
| 	strbuf_reset(&sb); | ||||
|  | ||||
| 	return needs_cloning; | ||||
| } | ||||
|  | ||||
| static int update_clone_get_next_task(struct child_process *child, | ||||
| 				      struct strbuf *err, | ||||
| 				      void *suc_cb, | ||||
| 				      void **void_task_cb) | ||||
| { | ||||
| 	struct submodule_update_clone *suc = suc_cb; | ||||
|  | ||||
| 	for (; suc->current < suc->list.nr; suc->current++) { | ||||
| 		const struct cache_entry *ce = suc->list.entries[suc->current]; | ||||
| 		if (prepare_to_clone_next_submodule(ce, child, suc, err)) { | ||||
| 			suc->current++; | ||||
| 			return 1; | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int update_clone_start_failure(struct strbuf *err, | ||||
| 				      void *suc_cb, | ||||
| 				      void *void_task_cb) | ||||
| { | ||||
| 	struct submodule_update_clone *suc = suc_cb; | ||||
| 	suc->quickstop = 1; | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| static int update_clone_task_finished(int result, | ||||
| 				      struct strbuf *err, | ||||
| 				      void *suc_cb, | ||||
| 				      void *void_task_cb) | ||||
| { | ||||
| 	struct submodule_update_clone *suc = suc_cb; | ||||
|  | ||||
| 	if (!result) | ||||
| 		return 0; | ||||
|  | ||||
| 	suc->quickstop = 1; | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| static int update_clone(int argc, const char **argv, const char *prefix) | ||||
| { | ||||
| 	const char *update = NULL; | ||||
| 	struct string_list_item *item; | ||||
| 	struct pathspec pathspec; | ||||
| 	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; | ||||
|  | ||||
| 	struct option module_update_clone_options[] = { | ||||
| 		OPT_STRING(0, "prefix", &prefix, | ||||
| 			   N_("path"), | ||||
| 			   N_("path into the working tree")), | ||||
| 		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix, | ||||
| 			   N_("path"), | ||||
| 			   N_("path into the working tree, across nested " | ||||
| 			      "submodule boundaries")), | ||||
| 		OPT_STRING(0, "update", &update, | ||||
| 			   N_("string"), | ||||
| 			   N_("rebase, merge, checkout or none")), | ||||
| 		OPT_STRING(0, "reference", &suc.reference, N_("repo"), | ||||
| 			   N_("reference repository")), | ||||
| 		OPT_STRING(0, "depth", &suc.depth, "<depth>", | ||||
| 			   N_("Create a shallow clone truncated to the " | ||||
| 			      "specified number of revisions")), | ||||
| 		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), | ||||
| 		OPT_END() | ||||
| 	}; | ||||
|  | ||||
| 	const char *const git_submodule_helper_usage[] = { | ||||
| 		N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"), | ||||
| 		NULL | ||||
| 	}; | ||||
| 	suc.prefix = prefix; | ||||
|  | ||||
| 	argc = parse_options(argc, argv, prefix, module_update_clone_options, | ||||
| 			     git_submodule_helper_usage, 0); | ||||
|  | ||||
| 	if (update) | ||||
| 		if (parse_submodule_update_strategy(update, &suc.update) < 0) | ||||
| 			die(_("bad value for update parameter")); | ||||
|  | ||||
| 	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) | ||||
| 		return 1; | ||||
|  | ||||
| 	if (pathspec.nr) | ||||
| 		suc.warn_if_uninitialized = 1; | ||||
|  | ||||
| 	/* Overlay the parsed .gitmodules file with .git/config */ | ||||
| 	gitmodules_config(); | ||||
| 	git_config(submodule_config, NULL); | ||||
|  | ||||
| 	run_processes_parallel(1, | ||||
| 			       update_clone_get_next_task, | ||||
| 			       update_clone_start_failure, | ||||
| 			       update_clone_task_finished, | ||||
| 			       &suc); | ||||
|  | ||||
| 	/* | ||||
| 	 * We saved the output and put it out all at once now. | ||||
| 	 * That means: | ||||
| 	 * - the listener does not have to interleave their (checkout) | ||||
| 	 *   work with our fetching.  The writes involved in a | ||||
| 	 *   checkout involve more straightforward sequential I/O. | ||||
| 	 * - the listener can avoid doing any work if fetching failed. | ||||
| 	 */ | ||||
| 	if (suc.quickstop) | ||||
| 		return 1; | ||||
|  | ||||
| 	for_each_string_list_item(item, &suc.projectlines) | ||||
| 		utf8_fprintf(stdout, "%s", item->string); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| struct cmd_struct { | ||||
| 	const char *cmd; | ||||
| 	int (*fn)(int, const char **, const char *); | ||||
|  | @ -264,6 +509,7 @@ static struct cmd_struct commands[] = { | |||
| 	{"list", module_list}, | ||||
| 	{"name", module_name}, | ||||
| 	{"clone", module_clone}, | ||||
| 	{"update-clone", update_clone} | ||||
| }; | ||||
|  | ||||
| int cmd_submodule__helper(int argc, const char **argv, const char *prefix) | ||||
|  |  | |||
|  | @ -664,17 +664,20 @@ cmd_update() | |||
| 		cmd_init "--" "$@" || return | ||||
| 	fi | ||||
|  | ||||
| 	cloned_modules= | ||||
| 	git submodule--helper list --prefix "$wt_prefix" "$@" | { | ||||
| 	{ | ||||
| 	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \ | ||||
| 		${wt_prefix:+--prefix "$wt_prefix"} \ | ||||
| 		${prefix:+--recursive-prefix "$prefix"} \ | ||||
| 		${update:+--update "$update"} \ | ||||
| 		${reference:+--reference "$reference"} \ | ||||
| 		${depth:+--depth "$depth"} \ | ||||
| 		"$@" || echo "#unmatched" | ||||
| 	} | { | ||||
| 	err= | ||||
| 	while read mode sha1 stage sm_path | ||||
| 	while read mode sha1 stage just_cloned sm_path | ||||
| 	do | ||||
| 		die_if_unmatched "$mode" | ||||
| 		if test "$stage" = U | ||||
| 		then | ||||
| 			echo >&2 "Skipping unmerged submodule $prefix$sm_path" | ||||
| 			continue | ||||
| 		fi | ||||
|  | ||||
| 		name=$(git submodule--helper name "$sm_path") || exit | ||||
| 		url=$(git config submodule."$name".url) | ||||
| 		branch=$(get_submodule_config "$name" branch master) | ||||
|  | @ -691,27 +694,10 @@ cmd_update() | |||
|  | ||||
| 		displaypath=$(relative_path "$prefix$sm_path") | ||||
|  | ||||
| 		if test "$update_module" = "none" | ||||
| 		if test $just_cloned -eq 1 | ||||
| 		then | ||||
| 			echo >&2 "Skipping submodule '$displaypath'" | ||||
| 			continue | ||||
| 		fi | ||||
|  | ||||
| 		if test -z "$url" | ||||
| 		then | ||||
| 			# Only mention uninitialized submodules when its | ||||
| 			# path have been specified | ||||
| 			test "$#" != "0" && | ||||
| 			say >&2 "$(eval_gettext "Submodule path '\$displaypath' not initialized | ||||
| Maybe you want to use 'update --init'?")" | ||||
| 			continue | ||||
| 		fi | ||||
|  | ||||
| 		if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git | ||||
| 		then | ||||
| 			git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit | ||||
| 			cloned_modules="$cloned_modules;$name" | ||||
| 			subsha1= | ||||
| 			update_module=checkout | ||||
| 		else | ||||
| 			subsha1=$(clear_local_git_env; cd "$sm_path" && | ||||
| 				git rev-parse --verify HEAD) || | ||||
|  | @ -751,13 +737,6 @@ Maybe you want to use 'update --init'?")" | |||
| 				die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")" | ||||
| 			fi | ||||
|  | ||||
| 			# Is this something we just cloned? | ||||
| 			case ";$cloned_modules;" in | ||||
| 			*";$name;"*) | ||||
| 				# then there is no local change to integrate | ||||
| 				update_module=checkout ;; | ||||
| 			esac | ||||
|  | ||||
| 			must_die_on_failure= | ||||
| 			case "$update_module" in | ||||
| 			checkout) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Stefan Beller
						Stefan Beller