Skip to content

Commit 02c6c14

Browse files
committed
Merge branch 'sb/submodule-clone-rr'
"git clone --resurse-submodules --reference $path $URL" is a way to reduce network transfer cost by borrowing objects in an existing $path repository when cloning the superproject from $URL; it learned to also peek into $path for presense of corresponding repositories of submodules and borrow objects from there when able. * sb/submodule-clone-rr: clone: recursive and reference option triggers submodule alternates clone: implement optional references clone: clarify option_reference as required clone: factor out checking for an alternate path submodule--helper update-clone: allow multiple references submodule--helper module-clone: allow multiple references t7408: merge short tests, factor out testing method t7408: modernize style
2 parents 00d2793 + 31224cb commit 02c6c14

File tree

8 files changed

+379
-121
lines changed

8 files changed

+379
-121
lines changed

Documentation/config.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2853,6 +2853,18 @@ submodule.fetchJobs::
28532853
in parallel. A value of 0 will give some reasonable default.
28542854
If unset, it defaults to 1.
28552855

2856+
submodule.alternateLocation::
2857+
Specifies how the submodules obtain alternates when submodules are
2858+
cloned. Possible values are `no`, `superproject`.
2859+
By default `no` is assumed, which doesn't add references. When the
2860+
value is set to `superproject` the submodule to be cloned computes
2861+
its alternates location relative to the superprojects alternate.
2862+
2863+
submodule.alternateErrorStrategy
2864+
Specifies how to treat errors with the alternates for a submodule
2865+
as computed via `submodule.alternateLocation`. Possible values are
2866+
`ignore`, `info`, `die`. Default is `die`.
2867+
28562868
tag.forceSignAnnotated::
28572869
A boolean to specify whether annotated tags created should be GPG signed.
28582870
If `--annotate` is specified on the command line, it takes

Documentation/git-clone.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,16 @@ If you want to break the dependency of a repository cloned with `-s` on
9090
its source repository, you can simply run `git repack -a` to copy all
9191
objects from the source repository into a pack in the cloned repository.
9292

93-
--reference <repository>::
93+
--reference[-if-able] <repository>::
9494
If the reference repository is on the local machine,
9595
automatically setup `.git/objects/info/alternates` to
9696
obtain objects from the reference repository. Using
9797
an already existing repository as an alternate will
9898
require fewer objects to be copied from the repository
9999
being cloned, reducing network and local storage costs.
100+
When using the `--reference-if-able`, a non existing
101+
directory is skipped with a warning instead of aborting
102+
the clone.
100103
+
101104
*NOTE*: see the NOTE for the `--shared` option, and also the
102105
`--dissociate` option.

builtin/clone.c

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ static int option_verbosity;
5050
static int option_progress = -1;
5151
static enum transport_family family;
5252
static struct string_list option_config = STRING_LIST_INIT_NODUP;
53-
static struct string_list option_reference = STRING_LIST_INIT_NODUP;
53+
static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
54+
static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
5455
static int option_dissociate;
5556
static int max_jobs = -1;
5657

@@ -79,8 +80,10 @@ static struct option builtin_clone_options[] = {
7980
N_("number of submodules cloned in parallel")),
8081
OPT_STRING(0, "template", &option_template, N_("template-directory"),
8182
N_("directory from which templates will be used")),
82-
OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
83+
OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"),
8384
N_("reference repository")),
85+
OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
86+
N_("repo"), N_("reference repository")),
8487
OPT_BOOL(0, "dissociate", &option_dissociate,
8588
N_("use --reference only while cloning")),
8689
OPT_STRING('o', "origin", &option_origin, N_("name"),
@@ -282,50 +285,37 @@ static void strip_trailing_slashes(char *dir)
282285

283286
static int add_one_reference(struct string_list_item *item, void *cb_data)
284287
{
285-
char *ref_git;
286-
const char *repo;
287-
struct strbuf alternate = STRBUF_INIT;
288-
289-
/* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
290-
ref_git = xstrdup(real_path(item->string));
291-
292-
repo = read_gitfile(ref_git);
293-
if (!repo)
294-
repo = read_gitfile(mkpath("%s/.git", ref_git));
295-
if (repo) {
296-
free(ref_git);
297-
ref_git = xstrdup(repo);
298-
}
288+
struct strbuf err = STRBUF_INIT;
289+
int *required = cb_data;
290+
char *ref_git = compute_alternate_path(item->string, &err);
299291

300-
if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
301-
char *ref_git_git = mkpathdup("%s/.git", ref_git);
302-
free(ref_git);
303-
ref_git = ref_git_git;
304-
} else if (!is_directory(mkpath("%s/objects", ref_git))) {
292+
if (!ref_git) {
293+
if (*required)
294+
die("%s", err.buf);
295+
else
296+
fprintf(stderr,
297+
_("info: Could not add alternate for '%s': %s\n"),
298+
item->string, err.buf);
299+
} else {
305300
struct strbuf sb = STRBUF_INIT;
306-
if (get_common_dir(&sb, ref_git))
307-
die(_("reference repository '%s' as a linked checkout is not supported yet."),
308-
item->string);
309-
die(_("reference repository '%s' is not a local repository."),
310-
item->string);
301+
strbuf_addf(&sb, "%s/objects", ref_git);
302+
add_to_alternates_file(sb.buf);
303+
strbuf_release(&sb);
311304
}
312305

313-
if (!access(mkpath("%s/shallow", ref_git), F_OK))
314-
die(_("reference repository '%s' is shallow"), item->string);
315-
316-
if (!access(mkpath("%s/info/grafts", ref_git), F_OK))
317-
die(_("reference repository '%s' is grafted"), item->string);
318-
319-
strbuf_addf(&alternate, "%s/objects", ref_git);
320-
add_to_alternates_file(alternate.buf);
321-
strbuf_release(&alternate);
306+
strbuf_release(&err);
322307
free(ref_git);
323308
return 0;
324309
}
325310

326311
static void setup_reference(void)
327312
{
328-
for_each_string_list(&option_reference, add_one_reference, NULL);
313+
int required = 1;
314+
for_each_string_list(&option_required_reference,
315+
add_one_reference, &required);
316+
required = 0;
317+
for_each_string_list(&option_optional_reference,
318+
add_one_reference, &required);
329319
}
330320

331321
static void copy_alternates(struct strbuf *src, struct strbuf *dst,
@@ -957,6 +947,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
957947
else
958948
fprintf(stderr, _("Cloning into '%s'...\n"), dir);
959949
}
950+
951+
if (option_recursive) {
952+
if (option_required_reference.nr &&
953+
option_optional_reference.nr)
954+
die(_("clone --recursive is not compatible with "
955+
"both --reference and --reference-if-able"));
956+
else if (option_required_reference.nr) {
957+
string_list_append(&option_config,
958+
"submodule.alternateLocation=superproject");
959+
string_list_append(&option_config,
960+
"submodule.alternateErrorStrategy=die");
961+
} else if (option_optional_reference.nr) {
962+
string_list_append(&option_config,
963+
"submodule.alternateLocation=superproject");
964+
string_list_append(&option_config,
965+
"submodule.alternateErrorStrategy=info");
966+
}
967+
}
968+
960969
init_db(option_template, INIT_DB_QUIET);
961970
write_config(&option_config);
962971

@@ -977,7 +986,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
977986
git_config_set(key.buf, repo);
978987
strbuf_reset(&key);
979988

980-
if (option_reference.nr)
989+
if (option_required_reference.nr || option_optional_reference.nr)
981990
setup_reference();
982991

983992
fetch_pattern = value.buf;

builtin/submodule--helper.c

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ static int module_name(int argc, const char **argv, const char *prefix)
442442
}
443443

444444
static int clone_submodule(const char *path, const char *gitdir, const char *url,
445-
const char *depth, const char *reference, int quiet)
445+
const char *depth, struct string_list *reference, int quiet)
446446
{
447447
struct child_process cp = CHILD_PROCESS_INIT;
448448

@@ -452,8 +452,12 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
452452
argv_array_push(&cp.args, "--quiet");
453453
if (depth && *depth)
454454
argv_array_pushl(&cp.args, "--depth", depth, NULL);
455-
if (reference && *reference)
456-
argv_array_pushl(&cp.args, "--reference", reference, NULL);
455+
if (reference->nr) {
456+
struct string_list_item *item;
457+
for_each_string_list_item(item, reference)
458+
argv_array_pushl(&cp.args, "--reference",
459+
item->string, NULL);
460+
}
457461
if (gitdir && *gitdir)
458462
argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
459463

@@ -467,15 +471,114 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
467471
return run_command(&cp);
468472
}
469473

474+
struct submodule_alternate_setup {
475+
const char *submodule_name;
476+
enum SUBMODULE_ALTERNATE_ERROR_MODE {
477+
SUBMODULE_ALTERNATE_ERROR_DIE,
478+
SUBMODULE_ALTERNATE_ERROR_INFO,
479+
SUBMODULE_ALTERNATE_ERROR_IGNORE
480+
} error_mode;
481+
struct string_list *reference;
482+
};
483+
#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
484+
SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
485+
486+
static int add_possible_reference_from_superproject(
487+
struct alternate_object_database *alt, void *sas_cb)
488+
{
489+
struct submodule_alternate_setup *sas = sas_cb;
490+
491+
/* directory name, minus trailing slash */
492+
size_t namelen = alt->name - alt->base - 1;
493+
struct strbuf name = STRBUF_INIT;
494+
strbuf_add(&name, alt->base, namelen);
495+
496+
/*
497+
* If the alternate object store is another repository, try the
498+
* standard layout with .git/modules/<name>/objects
499+
*/
500+
if (ends_with(name.buf, ".git/objects")) {
501+
char *sm_alternate;
502+
struct strbuf sb = STRBUF_INIT;
503+
struct strbuf err = STRBUF_INIT;
504+
strbuf_add(&sb, name.buf, name.len - strlen("objects"));
505+
/*
506+
* We need to end the new path with '/' to mark it as a dir,
507+
* otherwise a submodule name containing '/' will be broken
508+
* as the last part of a missing submodule reference would
509+
* be taken as a file name.
510+
*/
511+
strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
512+
513+
sm_alternate = compute_alternate_path(sb.buf, &err);
514+
if (sm_alternate) {
515+
string_list_append(sas->reference, xstrdup(sb.buf));
516+
free(sm_alternate);
517+
} else {
518+
switch (sas->error_mode) {
519+
case SUBMODULE_ALTERNATE_ERROR_DIE:
520+
die(_("submodule '%s' cannot add alternate: %s"),
521+
sas->submodule_name, err.buf);
522+
case SUBMODULE_ALTERNATE_ERROR_INFO:
523+
fprintf(stderr, _("submodule '%s' cannot add alternate: %s"),
524+
sas->submodule_name, err.buf);
525+
case SUBMODULE_ALTERNATE_ERROR_IGNORE:
526+
; /* nothing */
527+
}
528+
}
529+
strbuf_release(&sb);
530+
}
531+
532+
strbuf_release(&name);
533+
return 0;
534+
}
535+
536+
static void prepare_possible_alternates(const char *sm_name,
537+
struct string_list *reference)
538+
{
539+
char *sm_alternate = NULL, *error_strategy = NULL;
540+
struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT;
541+
542+
git_config_get_string("submodule.alternateLocation", &sm_alternate);
543+
if (!sm_alternate)
544+
return;
545+
546+
git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
547+
548+
if (!error_strategy)
549+
error_strategy = xstrdup("die");
550+
551+
sas.submodule_name = sm_name;
552+
sas.reference = reference;
553+
if (!strcmp(error_strategy, "die"))
554+
sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE;
555+
else if (!strcmp(error_strategy, "info"))
556+
sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO;
557+
else if (!strcmp(error_strategy, "ignore"))
558+
sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE;
559+
else
560+
die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy);
561+
562+
if (!strcmp(sm_alternate, "superproject"))
563+
foreach_alt_odb(add_possible_reference_from_superproject, &sas);
564+
else if (!strcmp(sm_alternate, "no"))
565+
; /* do nothing */
566+
else
567+
die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate);
568+
569+
free(sm_alternate);
570+
free(error_strategy);
571+
}
572+
470573
static int module_clone(int argc, const char **argv, const char *prefix)
471574
{
472-
const char *name = NULL, *url = NULL;
473-
const char *reference = NULL, *depth = NULL;
575+
const char *name = NULL, *url = NULL, *depth = NULL;
474576
int quiet = 0;
475577
FILE *submodule_dot_git;
476578
char *p, *path = NULL, *sm_gitdir;
477579
struct strbuf rel_path = STRBUF_INIT;
478580
struct strbuf sb = STRBUF_INIT;
581+
struct string_list reference = STRING_LIST_INIT_NODUP;
479582

480583
struct option module_clone_options[] = {
481584
OPT_STRING(0, "prefix", &prefix,
@@ -490,8 +593,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
490593
OPT_STRING(0, "url", &url,
491594
N_("string"),
492595
N_("url where to clone the submodule from")),
493-
OPT_STRING(0, "reference", &reference,
494-
N_("string"),
596+
OPT_STRING_LIST(0, "reference", &reference,
597+
N_("repo"),
495598
N_("reference repository")),
496599
OPT_STRING(0, "depth", &depth,
497600
N_("string"),
@@ -527,7 +630,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
527630
if (!file_exists(sm_gitdir)) {
528631
if (safe_create_leading_directories_const(sm_gitdir) < 0)
529632
die(_("could not create directory '%s'"), sm_gitdir);
530-
if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet))
633+
634+
prepare_possible_alternates(name, &reference);
635+
636+
if (clone_submodule(path, sm_gitdir, url, depth, &reference, quiet))
531637
die(_("clone of '%s' into submodule path '%s' failed"),
532638
url, path);
533639
} else {
@@ -579,7 +685,7 @@ struct submodule_update_clone {
579685
/* configuration parameters which are passed on to the children */
580686
int quiet;
581687
int recommend_shallow;
582-
const char *reference;
688+
struct string_list references;
583689
const char *depth;
584690
const char *recursive_prefix;
585691
const char *prefix;
@@ -595,7 +701,8 @@ struct submodule_update_clone {
595701
int failed_clones_nr, failed_clones_alloc;
596702
};
597703
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
598-
SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \
704+
SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, STRING_LIST_INIT_DUP, \
705+
NULL, NULL, NULL, \
599706
STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
600707

601708

@@ -705,8 +812,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
705812
argv_array_pushl(&child->args, "--path", sub->path, NULL);
706813
argv_array_pushl(&child->args, "--name", sub->name, NULL);
707814
argv_array_pushl(&child->args, "--url", url, NULL);
708-
if (suc->reference)
709-
argv_array_push(&child->args, suc->reference);
815+
if (suc->references.nr) {
816+
struct string_list_item *item;
817+
for_each_string_list_item(item, &suc->references)
818+
argv_array_pushl(&child->args, "--reference", item->string, NULL);
819+
}
710820
if (suc->depth)
711821
argv_array_push(&child->args, suc->depth);
712822

@@ -829,7 +939,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
829939
OPT_STRING(0, "update", &update,
830940
N_("string"),
831941
N_("rebase, merge, checkout or none")),
832-
OPT_STRING(0, "reference", &suc.reference, N_("repo"),
942+
OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
833943
N_("reference repository")),
834944
OPT_STRING(0, "depth", &suc.depth, "<depth>",
835945
N_("Create a shallow clone truncated to the "

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ extern struct alternate_object_database {
13441344
} *alt_odb_list;
13451345
extern void prepare_alt_odb(void);
13461346
extern void read_info_alternates(const char * relative_base, int depth);
1347+
extern char *compute_alternate_path(const char *path, struct strbuf *err);
13471348
extern void add_to_alternates_file(const char *reference);
13481349
typedef int alt_odb_fn(struct alternate_object_database *, void *);
13491350
extern int foreach_alt_odb(alt_odb_fn, void*);

git-submodule.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ cmd_update()
576576
${wt_prefix:+--prefix "$wt_prefix"} \
577577
${prefix:+--recursive-prefix "$prefix"} \
578578
${update:+--update "$update"} \
579-
${reference:+--reference "$reference"} \
579+
${reference:+"$reference"} \
580580
${depth:+--depth "$depth"} \
581581
${recommend_shallow:+"$recommend_shallow"} \
582582
${jobs:+$jobs} \

0 commit comments

Comments
 (0)