Merge branch 'sb/submodule-blanket-recursive'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:07 +0000 (13:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:07 +0000 (13:47 -0700)
Many commands learned to pay attention to submodule.recurse
configuration.

* sb/submodule-blanket-recursive:
builtin/fetch.c: respect 'submodule.recurse' option
builtin/push.c: respect 'submodule.recurse' option
builtin/grep.c: respect 'submodule.recurse' option
Introduce 'submodule.recurse' option for worktree manipulators
submodule loading: separate code path for .gitmodules and config overlay
reset/checkout/read-tree: unify config callback for submodule recursion
submodule test invocation: only pass additional arguments
submodule recursing: do not write a config variable twice

16 files changed:
Documentation/config.txt
builtin/checkout.c
builtin/fetch.c
builtin/grep.c
builtin/push.c
builtin/read-tree.c
builtin/reset.c
submodule.c
submodule.h
t/lib-submodule-update.sh
t/t1013-read-tree-submodule.sh
t/t2013-checkout-submodule.sh
t/t5526-fetch-submodules.sh
t/t5531-deep-submodule-push.sh
t/t7112-reset-submodule.sh
t/t7814-grep-recurse-submodules.sh
index dd4beec39dbe0bb328448c2ff7ca918b97016aa5..f6278a5ae6a1f554ddb8b9861d9067181b928bb5 100644 (file)
@@ -3091,6 +3091,11 @@ submodule.active::
        submodule's path to determine if the submodule is of interest to git
        commands.
 
+submodule.recurse::
+       Specifies if commands recurse into submodules by default. This
+       applies to all commands that have a `--recurse-submodules` option.
+       Defaults to false.
+
 submodule.fetchJobs::
        Specifies how many submodules are fetched/cloned at the same time.
        A positive integer allows up to that number of submodules fetched
index a6b2af39d3e881480193a38ff1ca3b65cdc55d94..1624eed7e7625c18da7ffb58f1b9ae456c3dbd77 100644 (file)
 #include "submodule-config.h"
 #include "submodule.h"
 
-static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-
 static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
        N_("git checkout [<options>] [<branch>] -- <file>..."),
        NULL,
 };
 
-static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
-{
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
-
-       return 0;
-}
-
 struct checkout_opts {
        int patch_mode;
        int quiet;
@@ -876,7 +857,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
        }
 
        if (starts_with(var, "submodule."))
-               return parse_submodule_config_option(var, value);
+               return submodule_config(var, value, NULL);
 
        return git_xmerge_config(var, value, NULL);
 }
@@ -1184,9 +1165,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                                N_("second guess 'git checkout <no-such-branch>'")),
                OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
                         N_("do not check if another worktree is holding the given ref")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
                OPT_END(),
        };
@@ -1217,12 +1198,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
        }
 
-       if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
-               git_config(submodule_config, NULL);
-               if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
-                       set_config_update_recurse_submodules(recurse_submodules);
-       }
-
        if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
 
index 47708451bc5e124f9c6da4de60cc476a5d2c1f10..100248c5afe3e1c16fa7fe79696200aeb5d1bde2 100644 (file)
@@ -73,6 +73,13 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
                fetch_prune_config = git_config_bool(k, v);
                return 0;
        }
+
+       if (!strcmp(k, "submodule.recurse")) {
+               int r = git_config_bool(k, v) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               recurse_submodules = r;
+       }
+
        return git_default_config(k, v, cb);
 }
 
index 7df9c253eebb3053693769d14efe0551393dfd4d..d188871556d814b1532f7118f1d76741850a370d 100644 (file)
@@ -302,6 +302,9 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
 #endif
        }
 
+       if (!strcmp(var, "submodule.recurse"))
+               recurse_submodules = git_config_bool(var, value);
+
        return st;
 }
 
index a597759d8fac205f41c406d0a0e32f9240941190..258648d5fd685f4284b3a2bbc0def0076a670b28 100644 (file)
@@ -498,6 +498,10 @@ static int git_push_config(const char *k, const char *v, void *cb)
                const char *value;
                if (!git_config_get_value("push.recursesubmodules", &value))
                        recurse_submodules = parse_push_recurse_submodules_arg(k, value);
+       } else if (!strcmp(k, "submodule.recurse")) {
+               int val = git_config_bool(k, v) ?
+                       RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF;
+               recurse_submodules = val;
        }
 
        return git_default_config(k, v, NULL);
index 78d3193659e06b4969324153689f219f1cd1c1b3..5bfd4c9f76d84c177b72a8ed97d3418d111583e9 100644 (file)
@@ -21,7 +21,6 @@
 static int nr_trees;
 static int read_empty;
 static struct tree *trees[MAX_UNPACK_TREES];
-static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 
 static int list_tree(struct object_id *oid)
 {
@@ -99,21 +98,12 @@ static int debug_merge(const struct cache_entry * const *stages,
        return 0;
 }
 
-static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
+static int git_read_tree_config(const char *var, const char *value, void *cb)
 {
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
+       if (!strcmp(var, "submodule.recurse"))
+               return git_default_submodule_config(var, value, cb);
 
-       return 0;
+       return git_default_config(var, value, cb);
 }
 
 static struct lock_file lock_file;
@@ -157,9 +147,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                         N_("skip applying sparse checkout filter")),
                OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
                         N_("debug unpack-trees")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_END()
        };
 
@@ -168,18 +158,14 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 
-       git_config(git_default_config, NULL);
+       git_config(git_read_tree_config, NULL);
 
        argc = parse_options(argc, argv, unused_prefix, read_tree_options,
                             read_tree_usage, 0);
 
-       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+       load_submodule_cache();
 
-       if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
-               gitmodules_config();
-               git_config(submodule_config, NULL);
-               set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
-       }
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        prefix_set = opts.prefix ? 1 : 0;
        if (1 < opts.merge + opts.reset + prefix_set)
index 430602d102133d125009e8c8068ce5547c24cab7..45001e5200cb4c5aaf0ad316cf1622f9495d851d 100644 (file)
 #include "submodule.h"
 #include "submodule-config.h"
 
-static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-
-static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
-{
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
-
-       return 0;
-}
-
 static const char * const git_reset_usage[] = {
        N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
        N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
@@ -284,6 +265,14 @@ static int reset_refs(const char *rev, const struct object_id *oid)
        return update_ref_status;
 }
 
+static int git_reset_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "submodule.recurse"))
+               return git_default_submodule_config(var, value, cb);
+
+       return git_default_config(var, value, cb);
+}
+
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
        int reset_type = NONE, update_ref_status = 0, quiet = 0;
@@ -303,26 +292,22 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                N_("reset HEAD, index and working tree"), MERGE),
                OPT_SET_INT(0, "keep", &reset_type,
                                N_("reset HEAD but keep local changes"), KEEP),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "reset", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
                OPT_BOOL('N', "intent-to-add", &intent_to_add,
                                N_("record only the fact that removed paths will be added later")),
                OPT_END()
        };
 
-       git_config(git_default_config, NULL);
+       git_config(git_reset_config, NULL);
 
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
        parse_args(&pathspec, argv, prefix, patch_mode, &rev);
 
-       if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
-               gitmodules_config();
-               git_config(submodule_config, NULL);
-               set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
-       }
+       load_submodule_cache();
 
        unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash);
        if (unborn) {
index bf5a93d16fb71cdeb87f589732b6f95f4a83fbe0..1b8a3b575db6c85a42e9c9e285617113ecac4a84 100644 (file)
 #include "quote.h"
 #include "remote.h"
 #include "worktree.h"
+#include "parse-options.h"
 
 static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
-static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
 static int parallel_jobs = 1;
 static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
 static int initialized_fetch_ref_tips;
@@ -153,7 +154,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
        }
 }
 
-int submodule_config(const char *var, const char *value, void *cb)
+/* For loading from the .gitmodules file. */
+static int git_modules_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "submodule.fetchjobs")) {
                parallel_jobs = git_config_int(var, value);
@@ -169,6 +171,56 @@ int submodule_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
+/* Loads all submodule settings from the config. */
+int submodule_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "submodule.recurse")) {
+               int v = git_config_bool(var, value) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               config_update_recurse_submodules = v;
+               return 0;
+       } else {
+               return git_modules_config(var, value, cb);
+       }
+}
+
+/* Cheap function that only determines if we're interested in submodules at all */
+int git_default_submodule_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "submodule.recurse")) {
+               int v = git_config_bool(var, value) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               config_update_recurse_submodules = v;
+       }
+       return 0;
+}
+
+int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
+                                                    const char *arg, int unset)
+{
+       if (unset) {
+               config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
+               return 0;
+       }
+       if (arg)
+               config_update_recurse_submodules =
+                       parse_update_recurse_submodules_arg(opt->long_name,
+                                                           arg);
+       else
+               config_update_recurse_submodules = RECURSE_SUBMODULES_ON;
+
+       return 0;
+}
+
+void load_submodule_cache(void)
+{
+       if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF)
+               return;
+
+       gitmodules_config();
+       git_config(submodule_config, NULL);
+}
+
 void gitmodules_config(void)
 {
        const char *work_tree = get_git_work_tree();
@@ -196,7 +248,8 @@ void gitmodules_config(void)
                }
 
                if (!gitmodules_is_unmerged)
-                       git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+                       git_config_from_file(git_modules_config,
+                               gitmodules_path.buf, NULL);
                strbuf_release(&gitmodules_path);
        }
 }
@@ -207,7 +260,7 @@ void gitmodules_config_sha1(const unsigned char *commit_sha1)
        unsigned char sha1[20];
 
        if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
-               git_config_from_blob_sha1(submodule_config, rev.buf,
+               git_config_from_blob_sha1(git_modules_config, rev.buf,
                                          sha1, NULL);
        }
        strbuf_release(&rev);
@@ -660,11 +713,6 @@ void set_config_fetch_recurse_submodules(int value)
        config_fetch_recurse_submodules = value;
 }
 
-void set_config_update_recurse_submodules(int value)
-{
-       config_update_recurse_submodules = value;
-}
-
 int should_update_submodules(void)
 {
        return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
index 8fb0f25498d58344adb86fee53770bb11cf36cbc..cbe5c1726f9adab2b032d872d4bd84425e6ce518 100644 (file)
@@ -39,6 +39,12 @@ extern void stage_updated_gitmodules(void);
 extern void set_diffopt_flags_from_submodule_config(struct diff_options *,
                const char *path);
 extern int submodule_config(const char *var, const char *value, void *cb);
+extern int git_default_submodule_config(const char *var, const char *value, void *cb);
+
+struct option;
+int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
+                                                    const char *arg, int unset);
+void load_submodule_cache(void);
 extern void gitmodules_config(void);
 extern void gitmodules_config_sha1(const unsigned char *commit_sha1);
 extern int is_submodule_initialized(const char *path);
@@ -69,7 +75,6 @@ extern void show_submodule_inline_diff(FILE *f, const char *path,
                const char *del, const char *add, const char *reset,
                const struct diff_options *opt);
 extern void set_config_fetch_recurse_submodules(int value);
-extern void set_config_update_recurse_submodules(int value);
 /* Check if we want to update any submodule.*/
 extern int should_update_submodules(void);
 /*
index 58bd4aeb2c52c67831a1dced2ef6b09755d417d3..2d26f86800906ab1783edf10e7196adadd5f4af7 100755 (executable)
@@ -781,8 +781,9 @@ test_submodule_forced_switch () {
 # - Removing a submodule with a git directory absorbs the submodules
 #   git directory first into the superproject.
 
-test_submodule_switch_recursing () {
-       command="$1"
+test_submodule_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
        RESULTDS=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
@@ -984,6 +985,18 @@ test_submodule_switch_recursing () {
                )
        '
 
+       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
+               prolog &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1
+               )
+       '
+
        # Updating a submodule to an invalid sha1 doesn't update the
        # superproject nor the submodule's work tree.
        test_expect_success "$command: updating to a missing submodule commit fails" '
@@ -1016,8 +1029,9 @@ test_submodule_switch_recursing () {
 # Test that submodule contents are updated when switching between commits
 # that change a submodule, but throwing away local changes in
 # the superproject as well as the submodule is allowed.
-test_submodule_forced_switch_recursing () {
-       command="$1"
+test_submodule_forced_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
        RESULT=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
index 7019d0a04feb7714f0ed8aad1d093c3a3878c78a..91a6fafcb425f367192c7eb4e4cdf0ae4d19ac37 100755 (executable)
@@ -8,9 +8,9 @@ test_description='read-tree can handle submodules'
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
 KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
-test_submodule_switch_recursing "git read-tree --recurse-submodules -u -m"
+test_submodule_switch_recursing_with_args "read-tree -u -m"
 
-test_submodule_forced_switch_recursing "git read-tree --recurse-submodules -u --reset"
+test_submodule_forced_switch_recursing_with_args "read-tree -u --reset"
 
 test_submodule_switch "git read-tree -u -m"
 
index aa3522336966749fbea6e8dbe6d8f32cf711a021..6ef15738e44ed8ad82960c042b3212fce266ec93 100755 (executable)
@@ -64,9 +64,9 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/
 '
 
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
-test_submodule_switch_recursing "git checkout --recurse-submodules"
+test_submodule_switch_recursing_with_args "checkout"
 
-test_submodule_forced_switch_recursing "git checkout -f --recurse-submodules"
+test_submodule_forced_switch_recursing_with_args "checkout -f"
 
 test_submodule_switch "git checkout"
 
index f3b0a8d30afcb472398bef8623a49ba6bac7dc24..162baf101f340885619e0a1e67b0b14cf37a2347 100755 (executable)
@@ -71,6 +71,16 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" '
        test_i18ncmp expect.err actual.err
 '
 
+test_expect_success "submodule.recurse option triggers recursive fetch" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git -c submodule.recurse fetch >../actual.out 2>../actual.err
+       ) &&
+       test_must_be_empty actual.out &&
+       test_i18ncmp expect.err actual.err
+'
+
 test_expect_success "fetch --recurse-submodules -j2 has the same output behaviour" '
        add_upstream_commit &&
        (
index 23c533e82e71e3b6ed26fc69313c4b51c36c3faa..beff65b8ace505d78c3997a737711ee7fc6383b5 100755 (executable)
@@ -126,6 +126,27 @@ test_expect_success 'push succeeds if submodule commit not on remote but using o
        )
 '
 
+test_expect_success 'push succeeds if submodule commit not on remote but using auto-on-demand via submodule.recurse config' '
+       (
+               cd work/gar/bage &&
+               >recurse-on-demand-from-submodule-recurse-config &&
+               git add recurse-on-demand-from-submodule-recurse-config &&
+               git commit -m "Recurse submodule.recurse from config junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Recurse submodule.recurse from config for gar/bage" &&
+               git -c submodule.recurse push ../pub.git master &&
+               # Check that the supermodule commit got there
+               git fetch ../pub.git &&
+               git diff --quiet FETCH_HEAD master &&
+               # Check that the submodule commit got there too
+               cd gar/bage &&
+               git diff --quiet origin/master master
+       )
+'
+
 test_expect_success 'push recurse-submodules on command line overrides config' '
        (
                cd work/gar/bage &&
index f86ccdf215abe725a0fb7ce35fb4760f25fb1b8c..a1cb9ff858e4a2113bbf7fb732b9e6d0cb308445 100755 (executable)
@@ -9,9 +9,9 @@ KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
 KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
-test_submodule_switch_recursing "git reset --recurse-submodules --keep"
+test_submodule_switch_recursing_with_args "reset --keep"
 
-test_submodule_forced_switch_recursing "git reset --hard --recurse-submodules"
+test_submodule_forced_switch_recursing_with_args "reset --hard"
 
 test_submodule_switch "git reset --keep"
 
index 3a58197f472f38c320de33fa82896c0a77d125ae..7184113b9b2b381a6e7e0def8a8c1ba06771f8a6 100755 (executable)
@@ -33,6 +33,24 @@ test_expect_success 'grep correctly finds patterns in a submodule' '
        test_cmp expect actual
 '
 
+test_expect_success 'grep finds patterns in a submodule via config' '
+       test_config submodule.recurse true &&
+       # expect from previous test
+       git grep -e "(3|4)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep --no-recurse-submodules overrides config' '
+       test_config submodule.recurse true &&
+       cat >expect <<-\EOF &&
+       a:(1|2)d(3|4)
+       b/b:(3|4)
+       EOF
+
+       git grep -e "(3|4)" --no-recurse-submodules >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'grep and basic pathspecs' '
        cat >expect <<-\EOF &&
        submodule/a:(1|2)d(3|4)