Skip to content

Commit bdebbeb

Browse files
committed
Merge branch 'sb/submodule-parallel-update'
A major part of "git submodule update" has been ported to C to take advantage of the recently added framework to run download tasks in parallel. * sb/submodule-parallel-update: clone: allow an explicit argument for parallel submodule clones submodule update: expose parallelism to the user submodule helper: remove double 'fatal: ' prefix git submodule update: have a dedicated helper for cloning run_processes_parallel: rename parameters for the callbacks run_processes_parallel: treat output of children as byte array submodule update: direct error message to stderr fetching submodules: respect `submodule.fetchJobs` config option submodule-config: drop check against NULL submodule-config: keep update strategy around
2 parents 77e0751 + 72290d6 commit bdebbeb

18 files changed

+445
-62
lines changed

Documentation/config.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,12 @@ submodule.<name>.ignore::
27382738
"--ignore-submodules" option. The 'git submodule' commands are not
27392739
affected by this setting.
27402740

2741+
submodule.fetchJobs::
2742+
Specifies how many submodules are fetched/cloned at the same time.
2743+
A positive integer allows up to that number of submodules fetched
2744+
in parallel. A value of 0 will give some reasonable default.
2745+
If unset, it defaults to 1.
2746+
27412747
tag.sort::
27422748
This variable controls the sort ordering of tags when displayed by
27432749
linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the

Documentation/git-clone.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ SYNOPSIS
1414
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
1515
[--dissociate] [--separate-git-dir <git dir>]
1616
[--depth <depth>] [--[no-]single-branch]
17-
[--recursive | --recurse-submodules] [--] <repository>
17+
[--recursive | --recurse-submodules] [--jobs <n>] [--] <repository>
1818
[<directory>]
1919

2020
DESCRIPTION
@@ -219,6 +219,10 @@ objects from the source repository into a pack in the cloned repository.
219219
The result is Git repository can be separated from working
220220
tree.
221221

222+
-j <n>::
223+
--jobs <n>::
224+
The number of submodules fetched at the same time.
225+
Defaults to the `submodule.fetchJobs` option.
222226

223227
<repository>::
224228
The (possibly remote) repository to clone from. See the

Documentation/git-submodule.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ SYNOPSIS
1616
'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
1717
'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
1818
[-f|--force] [--rebase|--merge] [--reference <repository>]
19-
[--depth <depth>] [--recursive] [--] [<path>...]
19+
[--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...]
2020
'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
2121
[commit] [--] [<path>...]
2222
'git submodule' [--quiet] foreach [--recursive] <command>
@@ -377,6 +377,11 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
377377
clone with a history truncated to the specified number of revisions.
378378
See linkgit:git-clone[1]
379379

380+
-j <n>::
381+
--jobs <n>::
382+
This option is only valid for the update command.
383+
Clone new submodules in parallel with as many jobs.
384+
Defaults to the `submodule.fetchJobs` option.
380385

381386
<path>...::
382387
Paths to submodule(s). When specified this will restrict the command

builtin/clone.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static enum transport_family family;
5151
static struct string_list option_config;
5252
static struct string_list option_reference;
5353
static int option_dissociate;
54+
static int max_jobs = -1;
5455

5556
static struct option builtin_clone_options[] = {
5657
OPT__VERBOSITY(&option_verbosity),
@@ -73,6 +74,8 @@ static struct option builtin_clone_options[] = {
7374
N_("initialize submodules in the clone")),
7475
OPT_BOOL(0, "recurse-submodules", &option_recursive,
7576
N_("initialize submodules in the clone")),
77+
OPT_INTEGER('j', "jobs", &max_jobs,
78+
N_("number of submodules cloned in parallel")),
7679
OPT_STRING(0, "template", &option_template, N_("template-directory"),
7780
N_("directory from which templates will be used")),
7881
OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
@@ -100,10 +103,6 @@ static struct option builtin_clone_options[] = {
100103
OPT_END()
101104
};
102105

103-
static const char *argv_submodule[] = {
104-
"submodule", "update", "--init", "--recursive", NULL
105-
};
106-
107106
static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
108107
{
109108
static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
@@ -732,8 +731,16 @@ static int checkout(void)
732731
err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
733732
sha1_to_hex(sha1), "1", NULL);
734733

735-
if (!err && option_recursive)
736-
err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
734+
if (!err && option_recursive) {
735+
struct argv_array args = ARGV_ARRAY_INIT;
736+
argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
737+
738+
if (max_jobs != -1)
739+
argv_array_pushf(&args, "--jobs=%d", max_jobs);
740+
741+
err = run_command_v_opt(args.argv, RUN_GIT_CMD);
742+
argv_array_clear(&args);
743+
}
737744

738745
return err;
739746
}

builtin/fetch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static int prune = -1; /* unspecified */
3737
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
3838
static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
3939
static int tags = TAGS_DEFAULT, unshallow, update_shallow;
40-
static int max_children = 1;
40+
static int max_children = -1;
4141
static enum transport_family family;
4242
static const char *depth;
4343
static const char *upload_pack;

builtin/submodule--helper.c

Lines changed: 254 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,257 @@ static int module_clone(int argc, const char **argv, const char *prefix)
249249
return 0;
250250
}
251251

252+
struct submodule_update_clone {
253+
/* index into 'list', the list of submodules to look into for cloning */
254+
int current;
255+
struct module_list list;
256+
unsigned warn_if_uninitialized : 1;
257+
258+
/* update parameter passed via commandline */
259+
struct submodule_update_strategy update;
260+
261+
/* configuration parameters which are passed on to the children */
262+
int quiet;
263+
const char *reference;
264+
const char *depth;
265+
const char *recursive_prefix;
266+
const char *prefix;
267+
268+
/* Machine-readable status lines to be consumed by git-submodule.sh */
269+
struct string_list projectlines;
270+
271+
/* If we want to stop as fast as possible and return an error */
272+
unsigned quickstop : 1;
273+
};
274+
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
275+
SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \
276+
STRING_LIST_INIT_DUP, 0}
277+
278+
/**
279+
* Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to
280+
* run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise.
281+
*/
282+
static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
283+
struct child_process *child,
284+
struct submodule_update_clone *suc,
285+
struct strbuf *out)
286+
{
287+
const struct submodule *sub = NULL;
288+
struct strbuf displaypath_sb = STRBUF_INIT;
289+
struct strbuf sb = STRBUF_INIT;
290+
const char *displaypath = NULL;
291+
char *url = NULL;
292+
int needs_cloning = 0;
293+
294+
if (ce_stage(ce)) {
295+
if (suc->recursive_prefix)
296+
strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name);
297+
else
298+
strbuf_addf(&sb, "%s", ce->name);
299+
strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf);
300+
strbuf_addch(out, '\n');
301+
goto cleanup;
302+
}
303+
304+
sub = submodule_from_path(null_sha1, ce->name);
305+
306+
if (suc->recursive_prefix)
307+
displaypath = relative_path(suc->recursive_prefix,
308+
ce->name, &displaypath_sb);
309+
else
310+
displaypath = ce->name;
311+
312+
if (suc->update.type == SM_UPDATE_NONE
313+
|| (suc->update.type == SM_UPDATE_UNSPECIFIED
314+
&& sub->update_strategy.type == SM_UPDATE_NONE)) {
315+
strbuf_addf(out, _("Skipping submodule '%s'"), displaypath);
316+
strbuf_addch(out, '\n');
317+
goto cleanup;
318+
}
319+
320+
/*
321+
* Looking up the url in .git/config.
322+
* We must not fall back to .gitmodules as we only want
323+
* to process configured submodules.
324+
*/
325+
strbuf_reset(&sb);
326+
strbuf_addf(&sb, "submodule.%s.url", sub->name);
327+
git_config_get_string(sb.buf, &url);
328+
if (!url) {
329+
/*
330+
* Only mention uninitialized submodules when their
331+
* path have been specified
332+
*/
333+
if (suc->warn_if_uninitialized) {
334+
strbuf_addf(out,
335+
_("Submodule path '%s' not initialized"),
336+
displaypath);
337+
strbuf_addch(out, '\n');
338+
strbuf_addstr(out,
339+
_("Maybe you want to use 'update --init'?"));
340+
strbuf_addch(out, '\n');
341+
}
342+
goto cleanup;
343+
}
344+
345+
strbuf_reset(&sb);
346+
strbuf_addf(&sb, "%s/.git", ce->name);
347+
needs_cloning = !file_exists(sb.buf);
348+
349+
strbuf_reset(&sb);
350+
strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode,
351+
sha1_to_hex(ce->sha1), ce_stage(ce),
352+
needs_cloning, ce->name);
353+
string_list_append(&suc->projectlines, sb.buf);
354+
355+
if (!needs_cloning)
356+
goto cleanup;
357+
358+
child->git_cmd = 1;
359+
child->no_stdin = 1;
360+
child->stdout_to_stderr = 1;
361+
child->err = -1;
362+
argv_array_push(&child->args, "submodule--helper");
363+
argv_array_push(&child->args, "clone");
364+
if (suc->quiet)
365+
argv_array_push(&child->args, "--quiet");
366+
if (suc->prefix)
367+
argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
368+
argv_array_pushl(&child->args, "--path", sub->path, NULL);
369+
argv_array_pushl(&child->args, "--name", sub->name, NULL);
370+
argv_array_pushl(&child->args, "--url", url, NULL);
371+
if (suc->reference)
372+
argv_array_push(&child->args, suc->reference);
373+
if (suc->depth)
374+
argv_array_push(&child->args, suc->depth);
375+
376+
cleanup:
377+
free(url);
378+
strbuf_reset(&displaypath_sb);
379+
strbuf_reset(&sb);
380+
381+
return needs_cloning;
382+
}
383+
384+
static int update_clone_get_next_task(struct child_process *child,
385+
struct strbuf *err,
386+
void *suc_cb,
387+
void **void_task_cb)
388+
{
389+
struct submodule_update_clone *suc = suc_cb;
390+
391+
for (; suc->current < suc->list.nr; suc->current++) {
392+
const struct cache_entry *ce = suc->list.entries[suc->current];
393+
if (prepare_to_clone_next_submodule(ce, child, suc, err)) {
394+
suc->current++;
395+
return 1;
396+
}
397+
}
398+
return 0;
399+
}
400+
401+
static int update_clone_start_failure(struct strbuf *err,
402+
void *suc_cb,
403+
void *void_task_cb)
404+
{
405+
struct submodule_update_clone *suc = suc_cb;
406+
suc->quickstop = 1;
407+
return 1;
408+
}
409+
410+
static int update_clone_task_finished(int result,
411+
struct strbuf *err,
412+
void *suc_cb,
413+
void *void_task_cb)
414+
{
415+
struct submodule_update_clone *suc = suc_cb;
416+
417+
if (!result)
418+
return 0;
419+
420+
suc->quickstop = 1;
421+
return 1;
422+
}
423+
424+
static int update_clone(int argc, const char **argv, const char *prefix)
425+
{
426+
const char *update = NULL;
427+
int max_jobs = -1;
428+
struct string_list_item *item;
429+
struct pathspec pathspec;
430+
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
431+
432+
struct option module_update_clone_options[] = {
433+
OPT_STRING(0, "prefix", &prefix,
434+
N_("path"),
435+
N_("path into the working tree")),
436+
OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
437+
N_("path"),
438+
N_("path into the working tree, across nested "
439+
"submodule boundaries")),
440+
OPT_STRING(0, "update", &update,
441+
N_("string"),
442+
N_("rebase, merge, checkout or none")),
443+
OPT_STRING(0, "reference", &suc.reference, N_("repo"),
444+
N_("reference repository")),
445+
OPT_STRING(0, "depth", &suc.depth, "<depth>",
446+
N_("Create a shallow clone truncated to the "
447+
"specified number of revisions")),
448+
OPT_INTEGER('j', "jobs", &max_jobs,
449+
N_("parallel jobs")),
450+
OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
451+
OPT_END()
452+
};
453+
454+
const char *const git_submodule_helper_usage[] = {
455+
N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"),
456+
NULL
457+
};
458+
suc.prefix = prefix;
459+
460+
argc = parse_options(argc, argv, prefix, module_update_clone_options,
461+
git_submodule_helper_usage, 0);
462+
463+
if (update)
464+
if (parse_submodule_update_strategy(update, &suc.update) < 0)
465+
die(_("bad value for update parameter"));
466+
467+
if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
468+
return 1;
469+
470+
if (pathspec.nr)
471+
suc.warn_if_uninitialized = 1;
472+
473+
/* Overlay the parsed .gitmodules file with .git/config */
474+
gitmodules_config();
475+
git_config(submodule_config, NULL);
476+
477+
if (max_jobs < 0)
478+
max_jobs = parallel_submodules();
479+
480+
run_processes_parallel(max_jobs,
481+
update_clone_get_next_task,
482+
update_clone_start_failure,
483+
update_clone_task_finished,
484+
&suc);
485+
486+
/*
487+
* We saved the output and put it out all at once now.
488+
* That means:
489+
* - the listener does not have to interleave their (checkout)
490+
* work with our fetching. The writes involved in a
491+
* checkout involve more straightforward sequential I/O.
492+
* - the listener can avoid doing any work if fetching failed.
493+
*/
494+
if (suc.quickstop)
495+
return 1;
496+
497+
for_each_string_list_item(item, &suc.projectlines)
498+
utf8_fprintf(stdout, "%s", item->string);
499+
500+
return 0;
501+
}
502+
252503
struct cmd_struct {
253504
const char *cmd;
254505
int (*fn)(int, const char **, const char *);
@@ -258,19 +509,20 @@ static struct cmd_struct commands[] = {
258509
{"list", module_list},
259510
{"name", module_name},
260511
{"clone", module_clone},
512+
{"update-clone", update_clone}
261513
};
262514

263515
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
264516
{
265517
int i;
266518
if (argc < 2)
267-
die(_("fatal: submodule--helper subcommand must be "
519+
die(_("submodule--helper subcommand must be "
268520
"called with a subcommand"));
269521

270522
for (i = 0; i < ARRAY_SIZE(commands); i++)
271523
if (!strcmp(argv[1], commands[i].cmd))
272524
return commands[i].fn(argc - 1, argv + 1, prefix);
273525

274-
die(_("fatal: '%s' is not a valid submodule--helper "
526+
die(_("'%s' is not a valid submodule--helper "
275527
"subcommand"), argv[1]);
276528
}

0 commit comments

Comments
 (0)