clone: teach --recurse-submodules to optionally take a pathspec

Teach clone --recurse-submodules to optionally take a pathspec argument which describes which submodules should be recursively initialized and cloned. If no pathspec is provided, --recurse-submodules will recursively initialize and clone all submodules by using a default pathspec of ".". In order to construct more complex pathspecs, --recurse-submodules can be given multiple times. This also configures the 'submodule.active' configuration option to be the given pathspec, such that any future invocation of `git submodule update` will keep up with the pathspec. Additionally the switch '--recurse' is removed from the Documentation as well as marked hidden in the options array, to streamline the options for submodules. A simple '--recurse' doesn't convey what is being recursed, e.g. it could mean directories or trees (c.f. ls-tree) In a lot of other commands we already have '--recurse-submodules' to mean recursing into submodules, so advertise this spelling here as the genuine option. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Mar 17, 2017 at 15:38 UTC bb62e0a99fcbb9ceb03502bf2168d2e57530214f
3 files changed +120 -12
Documentation/git-clone.txt
+9 -5
@@ -14,7 +14,7 @@ SYNOPSIS
14 [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15 [--dissociate] [--separate-git-dir <git dir>]
16 [--depth <depth>] [--[no-]single-branch]
17 - [--recursive | --recurse-submodules] [--[no-]shallow-submodules]
17 + [--recurse-submodules] [--[no-]shallow-submodules]
18 [--jobs <n>] [--] <repository> [<directory>]
19
20 DESCRIPTION
@@ -215,10 +215,14 @@ objects from the source repository into a pack in the cloned repository.
215 branch when `--single-branch` clone was made, no remote-tracking
216 branch is created.
217
218 ---recursive::
219 ---recurse-submodules::
220 - After the clone is created, initialize all submodules within,
221 - using their default settings. This is equivalent to running
218 +--recurse-submodules[=<pathspec]::
219 + After the clone is created, initialize and clone submodules
220 + within based on the provided pathspec. If no pathspec is
221 + provided, all submodules are initialized and cloned.
222 + Submodules are initialized and cloned using their default
223 + settings. The resulting clone has `submodule.active` set to
224 + the provided pathspec, or "." (meaning all submodules) if no
225 + pathspec is provided. This is equivalent to running
226 `git submodule update --init --recursive` immediately after
227 the clone is finished. This option is ignored if the cloned
228 repository does not have a worktree/checkout (i.e. if any of
builtin/clone.c
+43 -7
@@ -39,7 +39,7 @@ static const char * const builtin_clone_usage[] = {
39 };
40
41 static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
42 -static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
42 +static int option_local = -1, option_no_hardlinks, option_shared;
43 static int option_shallow_submodules;
44 static int deepen;
45 static char *option_template, *option_depth, *option_since;
@@ -56,6 +56,21 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
56 static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
57 static int option_dissociate;
58 static int max_jobs = -1;
59 +static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
60 +
61 +static int recurse_submodules_cb(const struct option *opt,
62 + const char *arg, int unset)
63 +{
64 + if (unset)
65 + string_list_clear((struct string_list *)opt->value, 0);
66 + else if (arg)
67 + string_list_append((struct string_list *)opt->value, arg);
68 + else
69 + string_list_append((struct string_list *)opt->value,
70 + (const char *)opt->defval);
71 +
72 + return 0;
73 +}
74
75 static struct option builtin_clone_options[] = {
76 OPT__VERBOSITY(&option_verbosity),
@@ -74,10 +89,13 @@ static struct option builtin_clone_options[] = {
89 N_("don't use local hardlinks, always copy")),
90 OPT_BOOL('s', "shared", &option_shared,
91 N_("setup as shared repository")),
77 - OPT_BOOL(0, "recursive", &option_recursive,
78 - N_("initialize submodules in the clone")),
79 - OPT_BOOL(0, "recurse-submodules", &option_recursive,
80 - N_("initialize submodules in the clone")),
92 + { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules,
93 + N_("pathspec"), N_("initialize submodules in the clone"),
94 + PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb,
95 + (intptr_t)"." },
96 + { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
97 + N_("pathspec"), N_("initialize submodules in the clone"),
98 + PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
99 OPT_INTEGER('j', "jobs", &max_jobs,
100 N_("number of submodules cloned in parallel")),
101 OPT_STRING(0, "template", &option_template, N_("template-directory"),
@@ -733,7 +751,7 @@ static int checkout(int submodule_progress)
751 err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
752 sha1_to_hex(sha1), "1", NULL);
753
736 - if (!err && option_recursive) {
754 + if (!err && (option_recurse_submodules.nr > 0)) {
755 struct argv_array args = ARGV_ARRAY_INIT;
756 argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
757
@@ -957,7 +975,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
975 fprintf(stderr, _("Cloning into '%s'...\n"), dir);
976 }
977
960 - if (option_recursive) {
978 + if (option_recurse_submodules.nr > 0) {
979 + struct string_list_item *item;
980 + struct strbuf sb = STRBUF_INIT;
981 +
982 + /* remove duplicates */
983 + string_list_sort(&option_recurse_submodules);
984 + string_list_remove_duplicates(&option_recurse_submodules, 0);
985 +
986 + /*
987 + * NEEDSWORK: In a multi-working-tree world, this needs to be
988 + * set in the per-worktree config.
989 + */
990 + for_each_string_list_item(item, &option_recurse_submodules) {
991 + strbuf_addf(&sb, "submodule.active=%s",
992 + item->string);
993 + string_list_append(&option_config,
994 + strbuf_detach(&sb, NULL));
995 + }
996 +
997 if (option_required_reference.nr &&
998 option_optional_reference.nr)
999 die(_("clone --recursive is not compatible with "
t/t7400-submodule-basic.sh
+68
@@ -1188,4 +1188,72 @@ test_expect_success 'submodule update and setting submodule.<name>.active' '
1188 test_cmp expect actual
1189 '
1190
1191 +test_expect_success 'clone --recurse-submodules with a pathspec works' '
1192 + test_when_finished "rm -rf multisuper_clone" &&
1193 + cat >expected <<-\EOF &&
1194 + sub0 (test2)
1195 + -sub1
1196 + -sub2
1197 + -sub3
1198 + EOF
1199 +
1200 + git clone --recurse-submodules="sub0" multisuper multisuper_clone &&
1201 + git -C multisuper_clone submodule status |cut -c1,43- >actual &&
1202 + test_cmp actual expected
1203 +'
1204 +
1205 +test_expect_success 'clone with multiple --recurse-submodules options' '
1206 + test_when_finished "rm -rf multisuper_clone" &&
1207 + cat >expect <<-\EOF &&
1208 + -sub0
1209 + sub1 (test2)
1210 + -sub2
1211 + sub3 (test2)
1212 + EOF
1213 +
1214 + git clone --recurse-submodules="." \
1215 + --recurse-submodules=":(exclude)sub0" \
1216 + --recurse-submodules=":(exclude)sub2" \
1217 + multisuper multisuper_clone &&
1218 + git -C multisuper_clone submodule status |cut -c1,43- >actual &&
1219 + test_cmp expect actual
1220 +'
1221 +
1222 +test_expect_success 'clone and subsequent updates correctly auto-initialize submodules' '
1223 + test_when_finished "rm -rf multisuper_clone" &&
1224 + cat <<-\EOF >expect &&
1225 + -sub0
1226 + sub1 (test2)
1227 + -sub2
1228 + sub3 (test2)
1229 + EOF
1230 +
1231 + cat <<-\EOF >expect2 &&
1232 + -sub0
1233 + sub1 (test2)
1234 + -sub2
1235 + sub3 (test2)
1236 + -sub4
1237 + sub5 (test2)
1238 + EOF
1239 +
1240 + git clone --recurse-submodules="." \
1241 + --recurse-submodules=":(exclude)sub0" \
1242 + --recurse-submodules=":(exclude)sub2" \
1243 + --recurse-submodules=":(exclude)sub4" \
1244 + multisuper multisuper_clone &&
1245 +
1246 + git -C multisuper_clone submodule status |cut -c1,43- >actual &&
1247 + test_cmp expect actual &&
1248 +
1249 + git -C multisuper submodule add ../sub1 sub4 &&
1250 + git -C multisuper submodule add ../sub1 sub5 &&
1251 + git -C multisuper commit -m "add more submodules" &&
1252 + # obtain the new superproject
1253 + git -C multisuper_clone pull &&
1254 + git -C multisuper_clone submodule update --init &&
1255 + git -C multisuper_clone submodule status |cut -c1,43- >actual &&
1256 + test_cmp expect2 actual
1257 +'
1258 +
1259 test_done