Skip to content

Commit

Permalink
grep: enable recurse-submodules to work on <tree> objects
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
bmwill authored and gitster committed Dec 22, 2016
1 parent 0281e48 commit 74ed437
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 9 deletions.
13 changes: 11 additions & 2 deletions Documentation/git-grep.txt
Expand Up @@ -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>...]

Expand Down Expand Up @@ -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::
Expand Down
76 changes: 70 additions & 6 deletions builtin/grep.c
Expand Up @@ -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>...]"),
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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")),
Expand Down Expand Up @@ -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)
Expand Down
103 changes: 102 additions & 1 deletion t/t7814-grep-recurse-submodules.sh
Expand Up @@ -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" "
Expand All @@ -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
28 changes: 28 additions & 0 deletions tree-walk.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

/*
Expand Down

0 comments on commit 74ed437

Please sign in to comment.