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>
			
			
				maint
			
			
		
							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
	
	 Michał Kiedrowicz
						Michał Kiedrowicz