Skip to content

Commit 74ed437

Browse files
bmwillgitster
authored andcommitted
grep: enable recurse-submodules to work on <tree> objects
Teach grep to recursively search in submodules when provided with a <tree> object. This allows grep to search a submodule based on the state of the submodule that is present in a commit of the super project. When grep is provided with a <tree> object, the name of the object is prefixed to all output. In order to provide uniformity of output between the parent and child processes the option `--parent-basename` has been added so that the child can preface all of it's output with the name of the parent's object instead of the name of the commit SHA1 of the submodule. This changes output from the command `git grep -e. -l --recurse-submodules HEAD` from: HEAD:file <commit sha1 of submodule>:sub/file to: HEAD:file HEAD:sub/file Signed-off-by: Brandon Williams <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0281e48 commit 74ed437

File tree

4 files changed

+211
-9
lines changed

4 files changed

+211
-9
lines changed

Documentation/git-grep.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ SYNOPSIS
2626
[--threads <num>]
2727
[-f <file>] [-e] <pattern>
2828
[--and|--or|--not|(|)|-e <pattern>...]
29-
[--recurse-submodules]
29+
[--recurse-submodules] [--parent-basename <basename>]
3030
[ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
3131
[--] [<pathspec>...]
3232

@@ -91,7 +91,16 @@ OPTIONS
9191

9292
--recurse-submodules::
9393
Recursively search in each submodule that has been initialized and
94-
checked out in the repository.
94+
checked out in the repository. When used in combination with the
95+
<tree> option the prefix of all submodule output will be the name of
96+
the parent project's <tree> object.
97+
98+
--parent-basename <basename>::
99+
For internal use only. In order to produce uniform output with the
100+
--recurse-submodules option, this option can be used to provide the
101+
basename of a parent's <tree> object to a submodule so the submodule
102+
can prefix its output with the parent's name rather than the SHA1 of
103+
the submodule.
95104

96105
-a::
97106
--text::

builtin/grep.c

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "dir.h"
2020
#include "pathspec.h"
2121
#include "submodule.h"
22+
#include "submodule-config.h"
2223

2324
static char const * const grep_usage[] = {
2425
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -28,6 +29,7 @@ static char const * const grep_usage[] = {
2829
static const char *super_prefix;
2930
static int recurse_submodules;
3031
static struct argv_array submodule_options = ARGV_ARRAY_INIT;
32+
static const char *parent_basename;
3133

3234
static int grep_submodule_launch(struct grep_opt *opt,
3335
const struct grep_source *gs);
@@ -534,19 +536,53 @@ static int grep_submodule_launch(struct grep_opt *opt,
534536
{
535537
struct child_process cp = CHILD_PROCESS_INIT;
536538
int status, i;
539+
const char *end_of_base;
540+
const char *name;
537541
struct work_item *w = opt->output_priv;
538542

543+
end_of_base = strchr(gs->name, ':');
544+
if (gs->identifier && end_of_base)
545+
name = end_of_base + 1;
546+
else
547+
name = gs->name;
548+
539549
prepare_submodule_repo_env(&cp.env_array);
540550

541551
/* Add super prefix */
542552
argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
543553
super_prefix ? super_prefix : "",
544-
gs->name);
554+
name);
545555
argv_array_push(&cp.args, "grep");
546556

557+
/*
558+
* Add basename of parent project
559+
* When performing grep on a tree object the filename is prefixed
560+
* with the object's name: 'tree-name:filename'. In order to
561+
* provide uniformity of output we want to pass the name of the
562+
* parent project's object name to the submodule so the submodule can
563+
* prefix its output with the parent's name and not its own SHA1.
564+
*/
565+
if (gs->identifier && end_of_base)
566+
argv_array_pushf(&cp.args, "--parent-basename=%.*s",
567+
(int) (end_of_base - gs->name),
568+
gs->name);
569+
547570
/* Add options */
548-
for (i = 0; i < submodule_options.argc; i++)
571+
for (i = 0; i < submodule_options.argc; i++) {
572+
/*
573+
* If there is a tree identifier for the submodule, add the
574+
* rev after adding the submodule options but before the
575+
* pathspecs. To do this we listen for the '--' and insert the
576+
* sha1 before pushing the '--' onto the child process argv
577+
* array.
578+
*/
579+
if (gs->identifier &&
580+
!strcmp("--", submodule_options.argv[i])) {
581+
argv_array_push(&cp.args, sha1_to_hex(gs->identifier));
582+
}
583+
549584
argv_array_push(&cp.args, submodule_options.argv[i]);
585+
}
550586

551587
cp.git_cmd = 1;
552588
cp.dir = gs->path;
@@ -673,12 +709,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
673709
enum interesting match = entry_not_interesting;
674710
struct name_entry entry;
675711
int old_baselen = base->len;
712+
struct strbuf name = STRBUF_INIT;
713+
int name_base_len = 0;
714+
if (super_prefix) {
715+
strbuf_addstr(&name, super_prefix);
716+
name_base_len = name.len;
717+
}
676718

677719
while (tree_entry(tree, &entry)) {
678720
int te_len = tree_entry_len(&entry);
679721

680722
if (match != all_entries_interesting) {
681-
match = tree_entry_interesting(&entry, base, tn_len, pathspec);
723+
strbuf_addstr(&name, base->buf + tn_len);
724+
match = tree_entry_interesting(&entry, &name,
725+
0, pathspec);
726+
strbuf_setlen(&name, name_base_len);
727+
682728
if (match == all_entries_not_interesting)
683729
break;
684730
if (match == entry_not_interesting)
@@ -690,8 +736,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
690736
if (S_ISREG(entry.mode)) {
691737
hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
692738
check_attr ? base->buf + tn_len : NULL);
693-
}
694-
else if (S_ISDIR(entry.mode)) {
739+
} else if (S_ISDIR(entry.mode)) {
695740
enum object_type type;
696741
struct tree_desc sub;
697742
void *data;
@@ -707,12 +752,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
707752
hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
708753
check_attr);
709754
free(data);
755+
} else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
756+
hit |= grep_submodule(opt, entry.oid->hash, base->buf,
757+
base->buf + tn_len);
710758
}
759+
711760
strbuf_setlen(base, old_baselen);
712761

713762
if (hit && opt->status_only)
714763
break;
715764
}
765+
766+
strbuf_release(&name);
716767
return hit;
717768
}
718769

@@ -736,6 +787,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
736787
if (!data)
737788
die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
738789

790+
/* Use parent's name as base when recursing submodules */
791+
if (recurse_submodules && parent_basename)
792+
name = parent_basename;
793+
739794
len = name ? strlen(name) : 0;
740795
strbuf_init(&base, PATH_MAX + len + 1);
741796
if (len) {
@@ -762,6 +817,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
762817
for (i = 0; i < nr; i++) {
763818
struct object *real_obj;
764819
real_obj = deref_tag(list->objects[i].item, NULL, 0);
820+
821+
/* load the gitmodules file for this rev */
822+
if (recurse_submodules) {
823+
submodule_free();
824+
gitmodules_config_sha1(real_obj->oid.hash);
825+
}
765826
if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) {
766827
hit = 1;
767828
if (opt->status_only)
@@ -902,6 +963,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
902963
N_("ignore files specified via '.gitignore'"), 1),
903964
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
904965
N_("recursivley search in each submodule")),
966+
OPT_STRING(0, "parent-basename", &parent_basename,
967+
N_("basename"),
968+
N_("prepend parent project's basename to output")),
905969
OPT_GROUP(""),
906970
OPT_BOOL('v', "invert-match", &opt.invert,
907971
N_("show non-matching lines")),
@@ -1154,7 +1218,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
11541218
}
11551219
}
11561220

1157-
if (recurse_submodules && (!use_index || untracked || list.nr))
1221+
if (recurse_submodules && (!use_index || untracked))
11581222
die(_("option not supported with --recurse-submodules."));
11591223

11601224
if (!show_in_pager && !opt.status_only)

t/t7814-grep-recurse-submodules.sh

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,108 @@ test_expect_success 'grep and multiple patterns' '
8484
test_cmp expect actual
8585
'
8686

87+
test_expect_success 'basic grep tree' '
88+
cat >expect <<-\EOF &&
89+
HEAD:a:foobar
90+
HEAD:b/b:bar
91+
HEAD:submodule/a:foobar
92+
HEAD:submodule/sub/a:foobar
93+
EOF
94+
95+
git grep -e "bar" --recurse-submodules HEAD >actual &&
96+
test_cmp expect actual
97+
'
98+
99+
test_expect_success 'grep tree HEAD^' '
100+
cat >expect <<-\EOF &&
101+
HEAD^:a:foobar
102+
HEAD^:b/b:bar
103+
HEAD^:submodule/a:foobar
104+
EOF
105+
106+
git grep -e "bar" --recurse-submodules HEAD^ >actual &&
107+
test_cmp expect actual
108+
'
109+
110+
test_expect_success 'grep tree HEAD^^' '
111+
cat >expect <<-\EOF &&
112+
HEAD^^:a:foobar
113+
HEAD^^:b/b:bar
114+
EOF
115+
116+
git grep -e "bar" --recurse-submodules HEAD^^ >actual &&
117+
test_cmp expect actual
118+
'
119+
120+
test_expect_success 'grep tree and pathspecs' '
121+
cat >expect <<-\EOF &&
122+
HEAD:submodule/a:foobar
123+
HEAD:submodule/sub/a:foobar
124+
EOF
125+
126+
git grep -e "bar" --recurse-submodules HEAD -- submodule >actual &&
127+
test_cmp expect actual
128+
'
129+
130+
test_expect_success 'grep tree and pathspecs' '
131+
cat >expect <<-\EOF &&
132+
HEAD:submodule/a:foobar
133+
HEAD:submodule/sub/a:foobar
134+
EOF
135+
136+
git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual &&
137+
test_cmp expect actual
138+
'
139+
140+
test_expect_success 'grep tree and more pathspecs' '
141+
cat >expect <<-\EOF &&
142+
HEAD:submodule/a:foobar
143+
EOF
144+
145+
git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual &&
146+
test_cmp expect actual
147+
'
148+
149+
test_expect_success 'grep tree and more pathspecs' '
150+
cat >expect <<-\EOF &&
151+
HEAD:submodule/sub/a:foobar
152+
EOF
153+
154+
git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual &&
155+
test_cmp expect actual
156+
'
157+
158+
test_expect_success !MINGW 'grep recurse submodule colon in name' '
159+
git init parent &&
160+
test_when_finished "rm -rf parent" &&
161+
echo "foobar" >"parent/fi:le" &&
162+
git -C parent add "fi:le" &&
163+
git -C parent commit -m "add fi:le" &&
164+
165+
git init "su:b" &&
166+
test_when_finished "rm -rf su:b" &&
167+
echo "foobar" >"su:b/fi:le" &&
168+
git -C "su:b" add "fi:le" &&
169+
git -C "su:b" commit -m "add fi:le" &&
170+
171+
git -C parent submodule add "../su:b" "su:b" &&
172+
git -C parent commit -m "add submodule" &&
173+
174+
cat >expect <<-\EOF &&
175+
fi:le:foobar
176+
su:b/fi:le:foobar
177+
EOF
178+
git -C parent grep -e "foobar" --recurse-submodules >actual &&
179+
test_cmp expect actual &&
180+
181+
cat >expect <<-\EOF &&
182+
HEAD:fi:le:foobar
183+
HEAD:su:b/fi:le:foobar
184+
EOF
185+
git -C parent grep -e "foobar" --recurse-submodules HEAD >actual &&
186+
test_cmp expect actual
187+
'
188+
87189
test_incompatible_with_recurse_submodules ()
88190
{
89191
test_expect_success "--recurse-submodules and $1 are incompatible" "
@@ -94,6 +196,5 @@ test_incompatible_with_recurse_submodules ()
94196

95197
test_incompatible_with_recurse_submodules --untracked
96198
test_incompatible_with_recurse_submodules --no-index
97-
test_incompatible_with_recurse_submodules HEAD
98199

99200
test_done

tree-walk.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry,
10041004
*/
10051005
if (ps->recursive && S_ISDIR(entry->mode))
10061006
return entry_interesting;
1007+
1008+
/*
1009+
* When matching against submodules with
1010+
* wildcard characters, ensure that the entry
1011+
* at least matches up to the first wild
1012+
* character. More accurate matching can then
1013+
* be performed in the submodule itself.
1014+
*/
1015+
if (ps->recursive && S_ISGITLINK(entry->mode) &&
1016+
!ps_strncmp(item, match + baselen,
1017+
entry->path,
1018+
item->nowildcard_len - baselen))
1019+
return entry_interesting;
10071020
}
10081021

10091022
continue;
@@ -1040,6 +1053,21 @@ static enum interesting do_match(const struct name_entry *entry,
10401053
strbuf_setlen(base, base_offset + baselen);
10411054
return entry_interesting;
10421055
}
1056+
1057+
/*
1058+
* When matching against submodules with
1059+
* wildcard characters, ensure that the entry
1060+
* at least matches up to the first wild
1061+
* character. More accurate matching can then
1062+
* be performed in the submodule itself.
1063+
*/
1064+
if (ps->recursive && S_ISGITLINK(entry->mode) &&
1065+
!ps_strncmp(item, match, base->buf + base_offset,
1066+
item->nowildcard_len)) {
1067+
strbuf_setlen(base, base_offset + baselen);
1068+
return entry_interesting;
1069+
}
1070+
10431071
strbuf_setlen(base, base_offset + baselen);
10441072

10451073
/*

0 commit comments

Comments
 (0)