diff options
| author | Michał Kiedrowicz <michal.kiedrowicz@gmail.com> | 2009-07-22 19:52:15 +0200 | 
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2009-07-22 21:54:54 -0700 | 
| commit | a91f453f641ca9966a438bdd3896656b00423407 (patch) | |
| tree | 8ba410fe118f0e5776ac9dd8631d5ceeeba0cd3e | |
| parent | 2a679c7a3148978a3f58f1c12100383638e744c5 (diff) | |
| download | git-a91f453f641ca9966a438bdd3896656b00423407.tar.gz | |
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>
| -rw-r--r-- | Documentation/git-grep.txt | 5 | ||||
| -rw-r--r-- | builtin-grep.c | 56 | ||||
| -rwxr-xr-x | contrib/completion/git-completion.bash | 1 | ||||
| -rw-r--r-- | grep.h | 1 | ||||
| -rwxr-xr-x | t/t7002-grep.sh | 51 | 
5 files changed, 103 insertions, 11 deletions
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index b753c9d76f..8c700200f5 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -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 diff --git a/builtin-grep.c b/builtin-grep.c index f477659100..ad0e0a5385 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -53,25 +53,57 @@ static int grep_config(const char *var, const char *value, void *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; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 887731e830..fb05c4884c 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -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  			" @@ -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; diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index b13aa7e89a..b4709e28b5 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -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  | 
