diff options
-rw-r--r-- | Documentation/git-grep.txt | 13 | ||||
-rw-r--r-- | builtin/grep.c | 76 | ||||
-rwxr-xr-x | t/t7814-grep-recurse-submodules.sh | 103 | ||||
-rw-r--r-- | tree-walk.c | 28 |
4 files changed, 211 insertions, 9 deletions
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 17aa1ba702..71f32f3508 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 dca0be6475..5918a26490 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 1019125e52..d5fc31638e 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 !MINGW '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 828f4356be..ff77605680 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 @@ match_wildcards: 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); /* |