git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v5 5/6] grep: enable recurse-submodules to work on <tree> objects

From
BWBrandon Williams <bmwill@google.com>
Date
Nov 22, 2016, 18:46 UTC
Message-ID
<1479840397-68264-6-git-send-email-bmwill@google.com>
In-Reply-To
<1479840397-68264-1-git-send-email-bmwill@google.com>

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>
---
 Documentation/git-grep.txt         |  13 ++++-
 builtin/grep.c                     |  76 ++++++++++++++++++++++++---
 t/t7814-grep-recurse-submodules.sh | 103 ++++++++++++++++++++++++++++++++++++-
 tree-walk.c                        |  28 ++++++++++
 4 files changed, 211 insertions(+), 9 deletions(-)
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 17aa1ba..71f32f3 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -26,7 +26,7 @@ SYNOPSIS
 	   [--threads <num>]
 	   [-f <file>] [-e] <pattern>
 	   [--and|--or|--not|(|)|-e <pattern>...]
-	   [--recurse-submodules]
+	   [--recurse-submodules] [--parent-basename <basename>]
 	   [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
 	   [--] [<pathspec>...]
 
@@ -91,7 +91,16 @@ OPTIONS
 
 --recurse-submodules::
 	Recursively search in each submodule that has been initialized and
-	checked out in the repository.
+	checked out in the repository.  When used in combination with the
+	<tree> option the prefix of all submodule output will be the name of
+	the parent project's <tree> object.
+
+--parent-basename <basename>::
+	For internal use only.  In order to produce uniform output with the
+	--recurse-submodules option, this option can be used to provide the
+	basename of a parent's <tree> object to a submodule so the submodule
+	can prefix its output with the parent's name rather than the SHA1 of
+	the submodule.
 
 -a::
 --text::
diff --git a/builtin/grep.c b/builtin/grep.c
index dca0be6..5918a26 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -19,6 +19,7 @@
 #include "dir.h"
 #include "pathspec.h"
 #include "submodule.h"
+#include "submodule-config.h"
 
 static char const * const grep_usage[] = {
 	N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -28,6 +29,7 @@ static char const * const grep_usage[] = {
 static const char *super_prefix;
 static int recurse_submodules;
 static struct argv_array submodule_options = ARGV_ARRAY_INIT;
+static const char *parent_basename;
 
 static int grep_submodule_launch(struct grep_opt *opt,
 				 const struct grep_source *gs);
@@ -534,19 +536,53 @@ static int grep_submodule_launch(struct grep_opt *opt,
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	int status, i;
+	const char *end_of_base;
+	const char *name;
 	struct work_item *w = opt->output_priv;
 
+	end_of_base = strchr(gs->name, ':');
+	if (gs->identifier && end_of_base)
+		name = end_of_base + 1;
+	else
+		name = gs->name;
+
 	prepare_submodule_repo_env(&cp.env_array);
 
 	/* Add super prefix */
 	argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
 			 super_prefix ? super_prefix : "",
-			 gs->name);
+			 name);
 	argv_array_push(&cp.args, "grep");
 
+	/*
+	 * Add basename of parent project
+	 * When performing grep on a tree object the filename is prefixed
+	 * with the object's name: 'tree-name:filename'.  In order to
+	 * provide uniformity of output we want to pass the name of the
+	 * parent project's object name to the submodule so the submodule can
+	 * prefix its output with the parent's name and not its own SHA1.
+	 */
+	if (gs->identifier && end_of_base)
+		argv_array_pushf(&cp.args, "--parent-basename=%.*s",
+				 (int) (end_of_base - gs->name),
+				 gs->name);
+
 	/* Add options */
-	for (i = 0; i < submodule_options.argc; i++)
+	for (i = 0; i < submodule_options.argc; i++) {
+		/*
+		 * If there is a tree identifier for the submodule, add the
+		 * rev after adding the submodule options but before the
+		 * pathspecs.  To do this we listen for the '--' and insert the
+		 * sha1 before pushing the '--' onto the child process argv
+		 * array.
+		 */
+		if (gs->identifier &&
+		    !strcmp("--", submodule_options.argv[i])) {
+			argv_array_push(&cp.args, sha1_to_hex(gs->identifier));
+		}
+
 		argv_array_push(&cp.args, submodule_options.argv[i]);
+	}
 
 	cp.git_cmd = 1;
 	cp.dir = gs->path;
@@ -673,12 +709,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
 	enum interesting match = entry_not_interesting;
 	struct name_entry entry;
 	int old_baselen = base->len;
+	struct strbuf name = STRBUF_INIT;
+	int name_base_len = 0;
+	if (super_prefix) {
+		strbuf_addstr(&name, super_prefix);
+		name_base_len = name.len;
+	}
 
 	while (tree_entry(tree, &entry)) {
 		int te_len = tree_entry_len(&entry);
 
 		if (match != all_entries_interesting) {
-			match = tree_entry_interesting(&entry, base, tn_len, pathspec);
+			strbuf_addstr(&name, base->buf + tn_len);
+			match = tree_entry_interesting(&entry, &name,
+						       0, pathspec);
+			strbuf_setlen(&name, name_base_len);
+
 			if (match == all_entries_not_interesting)
 				break;
 			if (match == entry_not_interesting)
@@ -690,8 +736,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
 		if (S_ISREG(entry.mode)) {
 			hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
 					 check_attr ? base->buf + tn_len : NULL);
-		}
-		else if (S_ISDIR(entry.mode)) {
+		} else if (S_ISDIR(entry.mode)) {
 			enum object_type type;
 			struct tree_desc sub;
 			void *data;
@@ -707,12 +752,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
 			hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
 					 check_attr);
 			free(data);
+		} else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
+			hit |= grep_submodule(opt, entry.oid->hash, base->buf,
+					      base->buf + tn_len);
 		}
+
 		strbuf_setlen(base, old_baselen);
 
 		if (hit && opt->status_only)
 			break;
 	}
+
+	strbuf_release(&name);
 	return hit;
 }
 
@@ -736,6 +787,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
 		if (!data)
 			die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
 
+		/* Use parent's name as base when recursing submodules */
+		if (recurse_submodules && parent_basename)
+			name = parent_basename;
+
 		len = name ? strlen(name) : 0;
 		strbuf_init(&base, PATH_MAX + len + 1);
 		if (len) {
@@ -762,6 +817,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
 	for (i = 0; i < nr; i++) {
 		struct object *real_obj;
 		real_obj = deref_tag(list->objects[i].item, NULL, 0);
+
+		/* load the gitmodules file for this rev */
+		if (recurse_submodules) {
+			submodule_free();
+			gitmodules_config_sha1(real_obj->oid.hash);
+		}
 		if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) {
 			hit = 1;
 			if (opt->status_only)
@@ -902,6 +963,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			    N_("ignore files specified via '.gitignore'"), 1),
 		OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
 			 N_("recursivley search in each submodule")),
+		OPT_STRING(0, "parent-basename", &parent_basename,
+			   N_("basename"),
+			   N_("prepend parent project's basename to output")),
 		OPT_GROUP(""),
 		OPT_BOOL('v', "invert-match", &opt.invert,
 			N_("show non-matching lines")),
@@ -1154,7 +1218,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (recurse_submodules && (!use_index || untracked || list.nr))
+	if (recurse_submodules && (!use_index || untracked))
 		die(_("option not supported with --recurse-submodules."));
 
 	if (!show_in_pager && !opt.status_only)
diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
index 1019125..9e93fe7 100755
--- a/t/t7814-grep-recurse-submodules.sh
+++ b/t/t7814-grep-recurse-submodules.sh
@@ -84,6 +84,108 @@ test_expect_success 'grep and multiple patterns' '
 	test_cmp expect actual
 '
 
+test_expect_success 'basic grep tree' '
+	cat >expect <<-\EOF &&
+	HEAD:a:foobar
+	HEAD:b/b:bar
+	HEAD:submodule/a:foobar
+	HEAD:submodule/sub/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree HEAD^' '
+	cat >expect <<-\EOF &&
+	HEAD^:a:foobar
+	HEAD^:b/b:bar
+	HEAD^:submodule/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD^ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree HEAD^^' '
+	cat >expect <<-\EOF &&
+	HEAD^^:a:foobar
+	HEAD^^:b/b:bar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD^^ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree and pathspecs' '
+	cat >expect <<-\EOF &&
+	HEAD:submodule/a:foobar
+	HEAD:submodule/sub/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD -- submodule >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree and pathspecs' '
+	cat >expect <<-\EOF &&
+	HEAD:submodule/a:foobar
+	HEAD:submodule/sub/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree and more pathspecs' '
+	cat >expect <<-\EOF &&
+	HEAD:submodule/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep tree and more pathspecs' '
+	cat >expect <<-\EOF &&
+	HEAD:submodule/sub/a:foobar
+	EOF
+
+	git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'grep recurse submodule colon in name' '
+	git init parent &&
+	test_when_finished "rm -rf parent" &&
+	echo "foobar" >"parent/fi:le" &&
+	git -C parent add "fi:le" &&
+	git -C parent commit -m "add fi:le" &&
+
+	git init "su:b" &&
+	test_when_finished "rm -rf su:b" &&
+	echo "foobar" >"su:b/fi:le" &&
+	git -C "su:b" add "fi:le" &&
+	git -C "su:b" commit -m "add fi:le" &&
+
+	git -C parent submodule add "../su:b" "su:b" &&
+	git -C parent commit -m "add submodule" &&
+
+	cat >expect <<-\EOF &&
+	fi:le:foobar
+	su:b/fi:le:foobar
+	EOF
+	git -C parent grep -e "foobar" --recurse-submodules >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+	HEAD:fi:le:foobar
+	HEAD:su:b/fi:le:foobar
+	EOF
+	git -C parent grep -e "foobar" --recurse-submodules HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_incompatible_with_recurse_submodules ()
 {
 	test_expect_success "--recurse-submodules and $1 are incompatible" "
@@ -94,6 +196,5 @@ test_incompatible_with_recurse_submodules ()
 
 test_incompatible_with_recurse_submodules --untracked
 test_incompatible_with_recurse_submodules --no-index
-test_incompatible_with_recurse_submodules HEAD
 
 test_done
diff --git a/tree-walk.c b/tree-walk.c
index 828f435..ff77605 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry,
 				 */
 				if (ps->recursive && S_ISDIR(entry->mode))
 					return entry_interesting;
+
+				/*
+				 * When matching against submodules with
+				 * wildcard characters, ensure that the entry
+				 * at least matches up to the first wild
+				 * character.  More accurate matching can then
+				 * be performed in the submodule itself.
+				 */
+				if (ps->recursive && S_ISGITLINK(entry->mode) &&
+				    !ps_strncmp(item, match + baselen,
+						entry->path,
+						item->nowildcard_len - baselen))
+					return entry_interesting;
 			}
 
 			continue;
@@ -1040,6 +1053,21 @@ static enum interesting do_match(const struct name_entry *entry,
 			strbuf_setlen(base, base_offset + baselen);
 			return entry_interesting;
 		}
+
+		/*
+		 * When matching against submodules with
+		 * wildcard characters, ensure that the entry
+		 * at least matches up to the first wild
+		 * character.  More accurate matching can then
+		 * be performed in the submodule itself.
+		 */
+		if (ps->recursive && S_ISGITLINK(entry->mode) &&
+		    !ps_strncmp(item, match, base->buf + base_offset,
+				item->nowildcard_len)) {
+			strbuf_setlen(base, base_offset + baselen);
+			return entry_interesting;
+		}
+
 		strbuf_setlen(base, base_offset + baselen);
 
 		/*
-- 
2.8.0.rc3.226.g39d4020
Previous: Brandon WilliamsNext: Junio C Hamano
Message 77 of 126 in “recursively grep across submodules”
  1. 0/5 recursively grep across submodulesBrandon Williams, Oct 27, 2016
  2. 1/5 submodules: add helper functions to determine presence of submodulesBrandon Williams, Oct 27, 2016
  3. 3/5 grep: add submodules as a grep source typeBrandon Williams, Oct 27, 2016
  4. 2/5 submodules: load gitmodules file from commit sha1Brandon Williams, Oct 27, 2016
  5. 4/5 grep: optionally recurse into submodulesBrandon Williams, Oct 27, 2016
  6. Jonathan TanNov 5, 2016
  7. 5/5 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Oct 27, 2016
  8. Brandon WilliamsOct 28, 2016
  9. Junio C HamanoOct 27, 2016
  10. Stefan BellerOct 28, 2016
  11. Junio C HamanoOct 28, 2016
  12. Stefan BellerOct 28, 2016
  13. Philip OakleyOct 28, 2016
  14. Brandon WilliamsOct 28, 2016
  15. Junio C HamanoOct 28, 2016
  16. 0/6 recursively grep across submodulesBrandon Williams, Oct 31, 2016
  17. 1/6 submodules: add helper functions to determine presence of submodulesBrandon Williams, Oct 31, 2016
  18. Stefan BellerOct 31, 2016
  19. Junio C HamanoNov 1, 2016
  20. Brandon WilliamsNov 1, 2016
  21. Stefan BellerNov 1, 2016
  22. Jacob KellerNov 6, 2016
  23. Brandon WilliamsNov 1, 2016
  24. Jonathan TanNov 5, 2016
  25. 2/6 submodules: load gitmodules file from commit sha1Brandon Williams, Oct 31, 2016
  26. Stefan BellerNov 1, 2016
  27. 3/6 grep: add submodules as a grep source typeBrandon Williams, Oct 31, 2016
  28. Stefan BellerNov 1, 2016
  29. Junio C HamanoNov 1, 2016
  30. 4/6 grep: optionally recurse into submodulesBrandon Williams, Oct 31, 2016
  31. Stefan BellerNov 1, 2016
  32. Brandon WilliamsNov 1, 2016
  33. 6/6 grep: search history of moved submodulesBrandon Williams, Oct 31, 2016
  34. 5/6 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Oct 31, 2016
  35. Jonathan TanNov 11, 2016
  36. 0/6 recursively grep across submodulesBrandon Williams, Nov 11, 2016
  37. 2/6 submodules: load gitmodules file from commit sha1Brandon Williams, Nov 11, 2016
  38. Stefan BellerNov 12, 2016
  39. 5/6 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Nov 11, 2016
  40. Junio C HamanoNov 14, 2016
  41. Jonathan TanNov 14, 2016
  42. Junio C HamanoNov 14, 2016
  43. Jonathan TanNov 14, 2016
  44. Brandon WilliamsNov 14, 2016
  45. Stefan BellerNov 16, 2016
  46. Brandon WilliamsNov 17, 2016
  47. 1/6 submodules: add helper functions to determine presence of submodulesBrandon Williams, Nov 11, 2016
  48. Stefan BellerNov 15, 2016
  49. 6/6 grep: search history of moved submodulesBrandon Williams, Nov 11, 2016
  50. Stefan BellerNov 12, 2016
  51. Brandon WilliamsNov 14, 2016
  52. 4/6 grep: optionally recurse into submodulesBrandon Williams, Nov 11, 2016
  53. Stefan BellerNov 16, 2016
  54. Brandon WilliamsNov 17, 2016
  55. 3/6 grep: add submodules as a grep source typeBrandon Williams, Nov 11, 2016
  56. Stefan BellerNov 15, 2016
  57. 0/6 recursively grep across submodulesBrandon Williams, Nov 18, 2016
  58. 1/6 submodules: add helper functions to determine presence of submodulesBrandon Williams, Nov 18, 2016
  59. 3/6 grep: add submodules as a grep source typeBrandon Williams, Nov 18, 2016
  60. Junio C HamanoNov 18, 2016
  61. Brandon WilliamsNov 18, 2016
  62. 2/6 submodules: load gitmodules file from commit sha1Brandon Williams, Nov 18, 2016
  63. 5/6 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Nov 18, 2016
  64. Junio C HamanoNov 18, 2016
  65. Brandon WilliamsNov 18, 2016
  66. Brandon WilliamsNov 21, 2016
  67. 6/6 grep: search history of moved submodulesBrandon Williams, Nov 18, 2016
  68. 4/6 grep: optionally recurse into submodulesBrandon Williams, Nov 18, 2016
  69. Junio C HamanoNov 18, 2016
  70. Junio C HamanoNov 18, 2016
  71. Junio C HamanoNov 18, 2016
  72. Brandon WilliamsNov 18, 2016
  73. Stefan BellerNov 18, 2016
  74. 0/6 recursively grep across submodulesBrandon Williams, Nov 22, 2016
  75. 3/6 grep: add submodules as a grep source typeBrandon Williams, Nov 22, 2016
  76. 1/6 submodules: add helper functions to determine presence of submodulesBrandon Williams, Nov 22, 2016
  77. 5/6 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Nov 22, 2016
  78. Junio C HamanoNov 22, 2016
  79. Brandon WilliamsNov 22, 2016
  80. Brandon WilliamsNov 22, 2016
  81. Junio C HamanoNov 22, 2016
  82. Brandon WilliamsNov 22, 2016
  83. 6/6 grep: search history of moved submodulesBrandon Williams, Nov 22, 2016
  84. 2/6 submodules: load gitmodules file from commit sha1Brandon Williams, Nov 22, 2016
  85. 4/6 grep: optionally recurse into submodulesBrandon Williams, Nov 22, 2016
  86. 0/6 recursively grep across submodulesBrandon Williams, Dec 1, 2016
  87. 1/6 submodules: add helper functions to determine presence of submodulesBrandon Williams, Dec 1, 2016
  88. Jeff KingDec 1, 2016
  89. Stefan BellerDec 1, 2016
  90. Junio C HamanoDec 1, 2016
  91. Jeff KingDec 1, 2016
  92. Brandon WilliamsDec 1, 2016
  93. Brandon WilliamsDec 1, 2016
  94. Jeff KingDec 1, 2016
  95. Stefan BellerDec 1, 2016
  96. Jeff KingDec 1, 2016
  97. Brandon WilliamsDec 2, 2016
  98. Jacob KellerDec 2, 2016
  99. Brandon WilliamsDec 2, 2016
  100. Jacob KellerDec 2, 2016
  101. Stefan BellerDec 2, 2016
  102. Jacob KellerDec 2, 2016
  103. Brandon WilliamsDec 2, 2016
  104. Jeff KingDec 2, 2016
  105. Brandon WilliamsDec 3, 2016
  106. 6/6 grep: search history of moved submodulesBrandon Williams, Dec 1, 2016
  107. 2/6 submodules: load gitmodules file from commit sha1Brandon Williams, Dec 1, 2016
  108. 3/6 grep: add submodules as a grep source typeBrandon Williams, Dec 1, 2016
  109. 4/6 grep: optionally recurse into submodulesBrandon Williams, Dec 1, 2016
  110. 5/6 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Dec 1, 2016
  111. Johannes SixtDec 1, 2016
  112. Brandon WilliamsDec 1, 2016
  113. Junio C HamanoDec 1, 2016
  114. Jeff KingDec 1, 2016
  115. Jeff KingDec 1, 2016
  116. Brandon WilliamsDec 1, 2016
  117. Jeff KingDec 1, 2016
  118. 0/7 recursively grep across submodulesBrandon Williams, Dec 16, 2016
  119. 1/7 submodules: add helper to determine if a submodule is populatedBrandon Williams, Dec 16, 2016
  120. 3/7 submodules: load gitmodules file from commit sha1Brandon Williams, Dec 16, 2016
  121. 2/7 submodules: add helper to determine if a submodule is initializedBrandon Williams, Dec 16, 2016
  122. 5/7 grep: optionally recurse into submodulesBrandon Williams, Dec 16, 2016
  123. 6/7 grep: enable recurse-submodules to work on <tree> objectsBrandon Williams, Dec 16, 2016
  124. 4/7 grep: add submodules as a grep source typeBrandon Williams, Dec 16, 2016
  125. 7/7 grep: search history of moved submodulesBrandon Williams, Dec 16, 2016
  126. Junio C HamanoDec 16, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.