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>
This commit is contained in:
parent
aa71049485
commit
48308681b0
@ -255,6 +255,251 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||||||
return 0;
|
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 {
|
struct cmd_struct {
|
||||||
const char *cmd;
|
const char *cmd;
|
||||||
int (*fn)(int, const char **, const char *);
|
int (*fn)(int, const char **, const char *);
|
||||||
@ -264,6 +509,7 @@ static struct cmd_struct commands[] = {
|
|||||||
{"list", module_list},
|
{"list", module_list},
|
||||||
{"name", module_name},
|
{"name", module_name},
|
||||||
{"clone", module_clone},
|
{"clone", module_clone},
|
||||||
|
{"update-clone", update_clone}
|
||||||
};
|
};
|
||||||
|
|
||||||
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
|
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
|
||||||
|
@ -664,17 +664,20 @@ cmd_update()
|
|||||||
cmd_init "--" "$@" || return
|
cmd_init "--" "$@" || return
|
||||||
fi
|
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=
|
err=
|
||||||
while read mode sha1 stage sm_path
|
while read mode sha1 stage just_cloned sm_path
|
||||||
do
|
do
|
||||||
die_if_unmatched "$mode"
|
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
|
name=$(git submodule--helper name "$sm_path") || exit
|
||||||
url=$(git config submodule."$name".url)
|
url=$(git config submodule."$name".url)
|
||||||
branch=$(get_submodule_config "$name" branch master)
|
branch=$(get_submodule_config "$name" branch master)
|
||||||
@ -691,27 +694,10 @@ cmd_update()
|
|||||||
|
|
||||||
displaypath=$(relative_path "$prefix$sm_path")
|
displaypath=$(relative_path "$prefix$sm_path")
|
||||||
|
|
||||||
if test "$update_module" = "none"
|
if test $just_cloned -eq 1
|
||||||
then
|
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=
|
subsha1=
|
||||||
|
update_module=checkout
|
||||||
else
|
else
|
||||||
subsha1=$(clear_local_git_env; cd "$sm_path" &&
|
subsha1=$(clear_local_git_env; cd "$sm_path" &&
|
||||||
git rev-parse --verify HEAD) ||
|
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'")"
|
die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
|
||||||
fi
|
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=
|
must_die_on_failure=
|
||||||
case "$update_module" in
|
case "$update_module" in
|
||||||
checkout)
|
checkout)
|
||||||
|
Loading…
Reference in New Issue
Block a user