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]
|
[-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
|
||||||
|
@ -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;
|
||||||
|
@ -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
1
grep.h
@ -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;
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user