@matheusbsbs / matheus-bagliano / commits / a91f453f64
Fork 0

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>

Michał Kiedrowicz committed Jul 22, 2009 at 19:52 UTC a91f453f641ca9966a438bdd3896656b00423407
5 files changed +103 -11
Documentation/git-grep.txt
+5
@@ -17,6 +17,7 @@ SYNOPSIS
17 [-l | --files-with-matches] [-L | --files-without-match]
18 [-z | --null]
19 [-c | --count] [--all-match]
20 + [--max-depth <depth>]
21 [--color | --no-color]
22 [-A <post-context>] [-B <pre-context>] [-C <context>]
23 [-f <file>] [-e] <pattern>
@@ -47,6 +48,10 @@ OPTIONS
48 -I::
49 Don't match the pattern in binary files.
50
51 +--max-depth <depth>::
52 + For each pathspec given on command line, descend at most <depth>
53 + levels of directories. A negative value means no limit.
54 +
55 -w::
56 --word-regexp::
57 Match the pattern only at word boundary (either begin at the
builtin-grep.c
+46 -10
@@ -52,26 +52,58 @@ static int grep_config(const char *var, const char *value, void *cb)
52 return git_color_default_config(var, value, cb);
53 }
54
55 +/*
56 + * Return non-zero if max_depth is negative or path has no more then max_depth
57 + * slashes.
58 + */
59 +static int accept_subdir(const char *path, int max_depth)
60 +{
61 + if (max_depth < 0)
62 + return 1;
63 +
64 + while ((path = strchr(path, '/')) != NULL) {
65 + max_depth--;
66 + if (max_depth < 0)
67 + return 0;
68 + path++;
69 + }
70 + return 1;
71 +}
72 +
73 +/*
74 + * Return non-zero if name is a subdirectory of match and is not too deep.
75 + */
76 +static int is_subdir(const char *name, int namelen,
77 + const char *match, int matchlen, int max_depth)
78 +{
79 + if (matchlen > namelen || strncmp(name, match, matchlen))
80 + return 0;
81 +
82 + if (name[matchlen] == '\0') /* exact match */
83 + return 1;
84 +
85 + if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
86 + return accept_subdir(name + matchlen + 1, max_depth);
87 +
88 + return 0;
89 +}
90 +
91 /*
92 * git grep pathspecs are somewhat different from diff-tree pathspecs;
93 * pathname wildcards are allowed.
94 */
59 -static int pathspec_matches(const char **paths, const char *name)
95 +static int pathspec_matches(const char **paths, const char *name, int max_depth)
96 {
97 int namelen, i;
98 if (!paths || !*paths)
63 - return 1;
99 + return accept_subdir(name, max_depth);
100 namelen = strlen(name);
101 for (i = 0; paths[i]; i++) {
102 const char *match = paths[i];
103 int matchlen = strlen(match);
104 const char *cp, *meta;
105
70 - if (!matchlen ||
71 - ((matchlen <= namelen) &&
72 - !strncmp(name, match, matchlen) &&
73 - (match[matchlen-1] == '/' ||
74 - name[matchlen] == '\0' || name[matchlen] == '/')))
106 + if (is_subdir(name, namelen, match, matchlen, max_depth))
107 return 1;
108 if (!fnmatch(match, name, 0))
109 return 1;
@@ -421,7 +453,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
453 int kept;
454 if (!S_ISREG(ce->ce_mode))
455 continue;
424 - if (!pathspec_matches(paths, ce->name))
456 + if (!pathspec_matches(paths, ce->name, opt->max_depth))
457 continue;
458 name = ce->name;
459 if (name[0] == '-') {
@@ -478,7 +510,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
510 struct cache_entry *ce = active_cache[nr];
511 if (!S_ISREG(ce->ce_mode))
512 continue;
481 - if (!pathspec_matches(paths, ce->name))
513 + if (!pathspec_matches(paths, ce->name, opt->max_depth))
514 continue;
515 /*
516 * 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,
570 strbuf_addch(&pathbuf, '/');
571
572 down = pathbuf.buf + tn_len;
541 - if (!pathspec_matches(paths, down))
573 + if (!pathspec_matches(paths, down, opt->max_depth))
574 ;
575 else if (S_ISREG(entry.mode))
576 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)
724 OPT_SET_INT('I', NULL, &opt.binary,
725 "don't match patterns in binary files",
726 GREP_BINARY_NOMATCH),
727 + { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
728 + "descend at most <depth> levels", PARSE_OPT_NONEG,
729 + NULL, 1 },
730 OPT_GROUP(""),
731 OPT_BIT('E', "extended-regexp", &opt.regflags,
732 "use extended POSIX regular expressions", REG_EXTENDED),
@@ -768,6 +803,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
803 opt.pathname = 1;
804 opt.pattern_tail = &opt.pattern_list;
805 opt.regflags = REG_NEWLINE;
806 + opt.max_depth = -1;
807
808 strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
809 opt.color = -1;
contrib/completion/git-completion.bash
+1
@@ -1036,6 +1036,7 @@ _git_grep ()
1036 --extended-regexp --basic-regexp --fixed-strings
1037 --files-with-matches --name-only
1038 --files-without-match
1039 + --max-depth
1040 --count
1041 --and --or --not --all-match
1042 "
grep.h
+1
@@ -79,6 +79,7 @@ struct grep_opt {
79 int pathname;
80 int null_following_name;
81 int color;
82 + int max_depth;
83 int funcname;
84 char color_match[COLOR_MAXLEN];
85 const char *color_external;
t/t7002-grep.sh
+50 -1
@@ -25,13 +25,17 @@ test_expect_success setup '
25 echo foo mmap bar_mmap
26 echo foo_mmap bar mmap baz
27 } >file &&
28 + echo vvv >v &&
29 echo ww w >w &&
30 echo x x xx x >x &&
31 echo y yy >y &&
32 echo zzz > z &&
33 mkdir t &&
34 echo test >t/t &&
34 - git add file w x y z t/t hello.c &&
35 + echo vvv >t/v &&
36 + mkdir t/a &&
37 + echo vvv >t/a/v &&
38 + git add . &&
39 test_tick &&
40 git commit -m initial
41 '
@@ -132,6 +136,51 @@ do
136 ! git grep -c test $H | grep /dev/null
137 '
138
139 + test_expect_success "grep --max-depth -1 $L" '
140 + {
141 + echo ${HC}t/a/v:1:vvv
142 + echo ${HC}t/v:1:vvv
143 + echo ${HC}v:1:vvv
144 + } >expected &&
145 + git grep --max-depth -1 -n -e vvv $H >actual &&
146 + test_cmp expected actual
147 + '
148 +
149 + test_expect_success "grep --max-depth 0 $L" '
150 + {
151 + echo ${HC}v:1:vvv
152 + } >expected &&
153 + git grep --max-depth 0 -n -e vvv $H >actual &&
154 + test_cmp expected actual
155 + '
156 +
157 + test_expect_success "grep --max-depth 0 -- '*' $L" '
158 + {
159 + echo ${HC}t/a/v:1:vvv
160 + echo ${HC}t/v:1:vvv
161 + echo ${HC}v:1:vvv
162 + } >expected &&
163 + git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
164 + test_cmp expected actual
165 + '
166 +
167 + test_expect_success "grep --max-depth 1 $L" '
168 + {
169 + echo ${HC}t/v:1:vvv
170 + echo ${HC}v:1:vvv
171 + } >expected &&
172 + git grep --max-depth 1 -n -e vvv $H >actual &&
173 + test_cmp expected actual
174 + '
175 +
176 + test_expect_success "grep --max-depth 0 -- t $L" '
177 + {
178 + echo ${HC}t/v:1:vvv
179 + } >expected &&
180 + git grep --max-depth 0 -n -e vvv $H -- t >actual &&
181 + test_cmp expected actual
182 + '
183 +
184 done
185
186 cat >expected <<EOF