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] | ||||
| 	   [-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
	
	 Michał Kiedrowicz
						Michał Kiedrowicz