grep: Add --max-depth option.

It is useful to grep directories non-recursively, e.g. when one wants to
look for all files in the toplevel directory, but not in any subdirectory,
or in Documentation/, but not in Documentation/technical/.

This patch adds support for --max-depth <depth> option to git-grep. If it is
given, git-grep descends at most <depth> levels of directories below paths
specified on the command line.

Note that if path specified on command line contains wildcards, this option
makes no sense, e.g.

    $ git grep -l --max-depth 0 GNU -- 'contrib/*'

(note the quotes) will search all files in contrib/, even in
subdirectories, because '*' matches all files.

Documentation updates, bash-completion and simple test cases are also
provided.

Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Michał Kiedrowicz 2009-07-22 19:52:15 +02:00 committed by Junio C Hamano
parent 2a679c7a31
commit a91f453f64
5 changed files with 103 additions and 11 deletions

View file

@ -17,6 +17,7 @@ SYNOPSIS
[-l | --files-with-matches] [-L | --files-without-match] [-l | --files-with-matches] [-L | --files-without-match]
[-z | --null] [-z | --null]
[-c | --count] [--all-match] [-c | --count] [--all-match]
[--max-depth <depth>]
[--color | --no-color] [--color | --no-color]
[-A <post-context>] [-B <pre-context>] [-C <context>] [-A <post-context>] [-B <pre-context>] [-C <context>]
[-f <file>] [-e] <pattern> [-f <file>] [-e] <pattern>
@ -47,6 +48,10 @@ OPTIONS
-I:: -I::
Don't match the pattern in binary files. Don't match the pattern in binary files.
--max-depth <depth>::
For each pathspec given on command line, descend at most <depth>
levels of directories. A negative value means no limit.
-w:: -w::
--word-regexp:: --word-regexp::
Match the pattern only at word boundary (either begin at the Match the pattern only at word boundary (either begin at the

View file

@ -52,26 +52,58 @@ static int grep_config(const char *var, const char *value, void *cb)
return git_color_default_config(var, value, cb); return git_color_default_config(var, value, cb);
} }
/*
* Return non-zero if max_depth is negative or path has no more then max_depth
* slashes.
*/
static int accept_subdir(const char *path, int max_depth)
{
if (max_depth < 0)
return 1;
while ((path = strchr(path, '/')) != NULL) {
max_depth--;
if (max_depth < 0)
return 0;
path++;
}
return 1;
}
/*
* Return non-zero if name is a subdirectory of match and is not too deep.
*/
static int is_subdir(const char *name, int namelen,
const char *match, int matchlen, int max_depth)
{
if (matchlen > namelen || strncmp(name, match, matchlen))
return 0;
if (name[matchlen] == '\0') /* exact match */
return 1;
if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
return accept_subdir(name + matchlen + 1, max_depth);
return 0;
}
/* /*
* git grep pathspecs are somewhat different from diff-tree pathspecs; * git grep pathspecs are somewhat different from diff-tree pathspecs;
* pathname wildcards are allowed. * pathname wildcards are allowed.
*/ */
static int pathspec_matches(const char **paths, const char *name) static int pathspec_matches(const char **paths, const char *name, int max_depth)
{ {
int namelen, i; int namelen, i;
if (!paths || !*paths) if (!paths || !*paths)
return 1; return accept_subdir(name, max_depth);
namelen = strlen(name); namelen = strlen(name);
for (i = 0; paths[i]; i++) { for (i = 0; paths[i]; i++) {
const char *match = paths[i]; const char *match = paths[i];
int matchlen = strlen(match); int matchlen = strlen(match);
const char *cp, *meta; const char *cp, *meta;
if (!matchlen || if (is_subdir(name, namelen, match, matchlen, max_depth))
((matchlen <= namelen) &&
!strncmp(name, match, matchlen) &&
(match[matchlen-1] == '/' ||
name[matchlen] == '\0' || name[matchlen] == '/')))
return 1; return 1;
if (!fnmatch(match, name, 0)) if (!fnmatch(match, name, 0))
return 1; return 1;
@ -421,7 +453,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
int kept; int kept;
if (!S_ISREG(ce->ce_mode)) if (!S_ISREG(ce->ce_mode))
continue; continue;
if (!pathspec_matches(paths, ce->name)) if (!pathspec_matches(paths, ce->name, opt->max_depth))
continue; continue;
name = ce->name; name = ce->name;
if (name[0] == '-') { if (name[0] == '-') {
@ -478,7 +510,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
struct cache_entry *ce = active_cache[nr]; struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode)) if (!S_ISREG(ce->ce_mode))
continue; continue;
if (!pathspec_matches(paths, ce->name)) if (!pathspec_matches(paths, ce->name, opt->max_depth))
continue; continue;
/* /*
* If CE_VALID is on, we assume worktree file and its cache entry * If CE_VALID is on, we assume worktree file and its cache entry
@ -538,7 +570,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
strbuf_addch(&pathbuf, '/'); strbuf_addch(&pathbuf, '/');
down = pathbuf.buf + tn_len; down = pathbuf.buf + tn_len;
if (!pathspec_matches(paths, down)) if (!pathspec_matches(paths, down, opt->max_depth))
; ;
else if (S_ISREG(entry.mode)) else if (S_ISREG(entry.mode))
hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
@ -692,6 +724,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_SET_INT('I', NULL, &opt.binary, OPT_SET_INT('I', NULL, &opt.binary,
"don't match patterns in binary files", "don't match patterns in binary files",
GREP_BINARY_NOMATCH), GREP_BINARY_NOMATCH),
{ OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
"descend at most <depth> levels", PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""), OPT_GROUP(""),
OPT_BIT('E', "extended-regexp", &opt.regflags, OPT_BIT('E', "extended-regexp", &opt.regflags,
"use extended POSIX regular expressions", REG_EXTENDED), "use extended POSIX regular expressions", REG_EXTENDED),
@ -768,6 +803,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.pathname = 1; opt.pathname = 1;
opt.pattern_tail = &opt.pattern_list; opt.pattern_tail = &opt.pattern_list;
opt.regflags = REG_NEWLINE; opt.regflags = REG_NEWLINE;
opt.max_depth = -1;
strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
opt.color = -1; opt.color = -1;

View file

@ -1036,6 +1036,7 @@ _git_grep ()
--extended-regexp --basic-regexp --fixed-strings --extended-regexp --basic-regexp --fixed-strings
--files-with-matches --name-only --files-with-matches --name-only
--files-without-match --files-without-match
--max-depth
--count --count
--and --or --not --all-match --and --or --not --all-match
" "

1
grep.h
View file

@ -79,6 +79,7 @@ struct grep_opt {
int pathname; int pathname;
int null_following_name; int null_following_name;
int color; int color;
int max_depth;
int funcname; int funcname;
char color_match[COLOR_MAXLEN]; char color_match[COLOR_MAXLEN];
const char *color_external; const char *color_external;

View file

@ -25,13 +25,17 @@ test_expect_success setup '
echo foo mmap bar_mmap echo foo mmap bar_mmap
echo foo_mmap bar mmap baz echo foo_mmap bar mmap baz
} >file && } >file &&
echo vvv >v &&
echo ww w >w && echo ww w >w &&
echo x x xx x >x && echo x x xx x >x &&
echo y yy >y && echo y yy >y &&
echo zzz > z && echo zzz > z &&
mkdir t && mkdir t &&
echo test >t/t && echo test >t/t &&
git add file w x y z t/t hello.c && echo vvv >t/v &&
mkdir t/a &&
echo vvv >t/a/v &&
git add . &&
test_tick && test_tick &&
git commit -m initial git commit -m initial
' '
@ -132,6 +136,51 @@ do
! git grep -c test $H | grep /dev/null ! git grep -c test $H | grep /dev/null
' '
test_expect_success "grep --max-depth -1 $L" '
{
echo ${HC}t/a/v:1:vvv
echo ${HC}t/v:1:vvv
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth -1 -n -e vvv $H >actual &&
test_cmp expected actual
'
test_expect_success "grep --max-depth 0 $L" '
{
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H >actual &&
test_cmp expected actual
'
test_expect_success "grep --max-depth 0 -- '*' $L" '
{
echo ${HC}t/a/v:1:vvv
echo ${HC}t/v:1:vvv
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
test_cmp expected actual
'
test_expect_success "grep --max-depth 1 $L" '
{
echo ${HC}t/v:1:vvv
echo ${HC}v:1:vvv
} >expected &&
git grep --max-depth 1 -n -e vvv $H >actual &&
test_cmp expected actual
'
test_expect_success "grep --max-depth 0 -- t $L" '
{
echo ${HC}t/v:1:vvv
} >expected &&
git grep --max-depth 0 -n -e vvv $H -- t >actual &&
test_cmp expected actual
'
done done
cat >expected <<EOF cat >expected <<EOF