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 <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Dec 16, 2016 at 11:03 UTC 74ed43711fd1cd7ce155d338f87ebe52cb74d9e2
4 files changed +211 -9
Documentation/git-grep.txt
+11 -2
@@ -26,7 +26,7 @@ SYNOPSIS
26 [--threads <num>]
27 [-f <file>] [-e] <pattern>
28 [--and|--or|--not|(|)|-e <pattern>...]
29 - [--recurse-submodules]
29 + [--recurse-submodules] [--parent-basename <basename>]
30 [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
31 [--] [<pathspec>...]
32
@@ -91,7 +91,16 @@ OPTIONS
91
92 --recurse-submodules::
93 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.
104
105 -a::
106 --text::
builtin/grep.c
+70 -6
@@ -19,6 +19,7 @@
19 #include "dir.h"
20 #include "pathspec.h"
21 #include "submodule.h"
22 +#include "submodule-config.h"
23
24 static char const * const grep_usage[] = {
25 N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -28,6 +29,7 @@ static char const * const grep_usage[] = {
29 static const char *super_prefix;
30 static int recurse_submodules;
31 static struct argv_array submodule_options = ARGV_ARRAY_INIT;
32 +static const char *parent_basename;
33
34 static int grep_submodule_launch(struct grep_opt *opt,
35 const struct grep_source *gs);
@@ -534,19 +536,53 @@ static int grep_submodule_launch(struct grep_opt *opt,
536 {
537 struct child_process cp = CHILD_PROCESS_INIT;
538 int status, i;
539 + const char *end_of_base;
540 + const char *name;
541 struct work_item *w = opt->output_priv;
542
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 +
549 prepare_submodule_repo_env(&cp.env_array);
550
551 /* Add super prefix */
552 argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
553 super_prefix ? super_prefix : "",
544 - gs->name);
554 + name);
555 argv_array_push(&cp.args, "grep");
556
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 +
570 /* 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 +
584 argv_array_push(&cp.args, submodule_options.argv[i]);
585 + }
586
587 cp.git_cmd = 1;
588 cp.dir = gs->path;
@@ -673,12 +709,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
709 enum interesting match = entry_not_interesting;
710 struct name_entry entry;
711 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 + }
718
719 while (tree_entry(tree, &entry)) {
720 int te_len = tree_entry_len(&entry);
721
722 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 +
728 if (match == all_entries_not_interesting)
729 break;
730 if (match == entry_not_interesting)
@@ -690,8 +736,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
736 if (S_ISREG(entry.mode)) {
737 hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
738 check_attr ? base->buf + tn_len : NULL);
693 - }
694 - else if (S_ISDIR(entry.mode)) {
739 + } else if (S_ISDIR(entry.mode)) {
740 enum object_type type;
741 struct tree_desc sub;
742 void *data;
@@ -707,12 +752,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
752 hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
753 check_attr);
754 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);
758 }
759 +
760 strbuf_setlen(base, old_baselen);
761
762 if (hit && opt->status_only)
763 break;
764 }
765 +
766 + strbuf_release(&name);
767 return hit;
768 }
769
@@ -736,6 +787,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
787 if (!data)
788 die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
789
790 + /* Use parent's name as base when recursing submodules */
791 + if (recurse_submodules && parent_basename)
792 + name = parent_basename;
793 +
794 len = name ? strlen(name) : 0;
795 strbuf_init(&base, PATH_MAX + len + 1);
796 if (len) {
@@ -762,6 +817,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
817 for (i = 0; i < nr; i++) {
818 struct object *real_obj;
819 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 + }
826 if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) {
827 hit = 1;
828 if (opt->status_only)
@@ -902,6 +963,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
963 N_("ignore files specified via '.gitignore'"), 1),
964 OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
965 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")),
969 OPT_GROUP(""),
970 OPT_BOOL('v', "invert-match", &opt.invert,
971 N_("show non-matching lines")),
@@ -1154,7 +1218,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
1218 }
1219 }
1220
1157 - if (recurse_submodules && (!use_index || untracked || list.nr))
1221 + if (recurse_submodules && (!use_index || untracked))
1222 die(_("option not supported with --recurse-submodules."));
1223
1224 if (!show_in_pager && !opt.status_only)
t/t7814-grep-recurse-submodules.sh
+102 -1
@@ -84,6 +84,108 @@ test_expect_success 'grep and multiple patterns' '
84 test_cmp expect actual
85 '
86
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 +
189 test_incompatible_with_recurse_submodules ()
190 {
191 test_expect_success "--recurse-submodules and $1 are incompatible" "
@@ -94,6 +196,5 @@ test_incompatible_with_recurse_submodules ()
196
197 test_incompatible_with_recurse_submodules --untracked
198 test_incompatible_with_recurse_submodules --no-index
97 -test_incompatible_with_recurse_submodules HEAD
199
200 test_done
tree-walk.c
+28
@@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry,
1004 */
1005 if (ps->recursive && S_ISDIR(entry->mode))
1006 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;
1020 }
1021
1022 continue;
@@ -1040,6 +1053,21 @@ match_wildcards:
1053 strbuf_setlen(base, base_offset + baselen);
1054 return entry_interesting;
1055 }
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 +
1071 strbuf_setlen(base, base_offset + baselen);
1072
1073 /*