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:
parent
2a679c7a31
commit
a91f453f64
@ -17,6 +17,7 @@ SYNOPSIS
|
||||
[-l | --files-with-matches] [-L | --files-without-match]
|
||||
[-z | --null]
|
||||
[-c | --count] [--all-match]
|
||||
[--max-depth <depth>]
|
||||
[--color | --no-color]
|
||||
[-A <post-context>] [-B <pre-context>] [-C <context>]
|
||||
[-f <file>] [-e] <pattern>
|
||||
@ -47,6 +48,10 @@ OPTIONS
|
||||
-I::
|
||||
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::
|
||||
--word-regexp::
|
||||
Match the pattern only at word boundary (either begin at the
|
||||
|
@ -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 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;
|
||||
* 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;
|
||||
if (!paths || !*paths)
|
||||
return 1;
|
||||
return accept_subdir(name, max_depth);
|
||||
namelen = strlen(name);
|
||||
for (i = 0; paths[i]; i++) {
|
||||
const char *match = paths[i];
|
||||
int matchlen = strlen(match);
|
||||
const char *cp, *meta;
|
||||
|
||||
if (!matchlen ||
|
||||
((matchlen <= namelen) &&
|
||||
!strncmp(name, match, matchlen) &&
|
||||
(match[matchlen-1] == '/' ||
|
||||
name[matchlen] == '\0' || name[matchlen] == '/')))
|
||||
if (is_subdir(name, namelen, match, matchlen, max_depth))
|
||||
return 1;
|
||||
if (!fnmatch(match, name, 0))
|
||||
return 1;
|
||||
@ -421,7 +453,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
||||
int kept;
|
||||
if (!S_ISREG(ce->ce_mode))
|
||||
continue;
|
||||
if (!pathspec_matches(paths, ce->name))
|
||||
if (!pathspec_matches(paths, ce->name, opt->max_depth))
|
||||
continue;
|
||||
name = ce->name;
|
||||
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];
|
||||
if (!S_ISREG(ce->ce_mode))
|
||||
continue;
|
||||
if (!pathspec_matches(paths, ce->name))
|
||||
if (!pathspec_matches(paths, ce->name, opt->max_depth))
|
||||
continue;
|
||||
/*
|
||||
* 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, '/');
|
||||
|
||||
down = pathbuf.buf + tn_len;
|
||||
if (!pathspec_matches(paths, down))
|
||||
if (!pathspec_matches(paths, down, opt->max_depth))
|
||||
;
|
||||
else if (S_ISREG(entry.mode))
|
||||
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,
|
||||
"don't match patterns in binary files",
|
||||
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_BIT('E', "extended-regexp", &opt.regflags,
|
||||
"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.pattern_tail = &opt.pattern_list;
|
||||
opt.regflags = REG_NEWLINE;
|
||||
opt.max_depth = -1;
|
||||
|
||||
strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
|
||||
opt.color = -1;
|
||||
|
@ -1036,6 +1036,7 @@ _git_grep ()
|
||||
--extended-regexp --basic-regexp --fixed-strings
|
||||
--files-with-matches --name-only
|
||||
--files-without-match
|
||||
--max-depth
|
||||
--count
|
||||
--and --or --not --all-match
|
||||
"
|
||||
|
1
grep.h
1
grep.h
@ -79,6 +79,7 @@ struct grep_opt {
|
||||
int pathname;
|
||||
int null_following_name;
|
||||
int color;
|
||||
int max_depth;
|
||||
int funcname;
|
||||
char color_match[COLOR_MAXLEN];
|
||||
const char *color_external;
|
||||
|
@ -25,13 +25,17 @@ test_expect_success setup '
|
||||
echo foo mmap bar_mmap
|
||||
echo foo_mmap bar mmap baz
|
||||
} >file &&
|
||||
echo vvv >v &&
|
||||
echo ww w >w &&
|
||||
echo x x xx x >x &&
|
||||
echo y yy >y &&
|
||||
echo zzz > z &&
|
||||
mkdir 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 &&
|
||||
git commit -m initial
|
||||
'
|
||||
@ -132,6 +136,51 @@ do
|
||||
! 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
|
||||
|
||||
cat >expected <<EOF
|
||||
|
Loading…
Reference in New Issue
Block a user