{"thread":{"id":"20196","subject":"[PATCH v4] grep: Add --max-depth option.","startedAt":"2009-07-22T17:52:15Z","lastAt":"2009-07-23T06:11:33Z","messageCount":3,"participants":["Michał Kiedrowicz","Junio C Hamano","Stephen Boyd"],"isPatch":true,"patchVersion":4,"patchTotal":null},"messages":[{"id":"118470","messageId":"1248285135-9033-1-git-send-email-michal.kiedrowicz@gmail.com","threadId":"20196","inReplyTo":null,"subject":"[PATCH v4] grep: Add --max-depth option.","fromName":"Michał Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2009-07-22T17:52:15Z","receivedAt":"2009-07-22T17:52:15Z","isPatch":true,"sender":{"key":"michal.kiedrowicz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/14072847?v=4"},"body":"Sometimes it is useful to grep directories non-recursive. E.g. if one\nwants to look for all files in main directory, but not in any subdirectory.\nOr in Documentation/, but not in Documentation/technical/ and so on.\n\nThis patch adds support for --max-depth <depth> option to git-grep. If it is\nset, git-grep descends at most <depth> levels of directories below paths\nspecified on command line.\n\nNote that if path specified on command line contains wildcards, option\n--max-depth makes no sense, i.e.\n\n$ git grep -l --max-depth 0 GNU -- 'contrib/*'\n\n(note the quotes) will search all files in contrib/, even in\nsubdirectories, because '*' matches all files.\n\nDocumentation updates, bash-completion and simple test cases are also\nprovided.\n\nSigned-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\n---\nI've merged count_chars() into accept_subdir(), changed OPT_INTEGER to\nOPTION_INTEGER, which allowed me to set argh to \"<depth>\", to be more\nconsistent with git-grep.txt, and revised documentation.\n\n Documentation/git-grep.txt             |    5 +++\n builtin-grep.c                         |   56 ++++++++++++++++++++++++++------\n contrib/completion/git-completion.bash |    1 +\n grep.h                                 |    1 +\n t/t7002-grep.sh                        |   51 ++++++++++++++++++++++++++++-\n 5 files changed, 103 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex b753c9d..d0e3638 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -17,6 +17,7 @@ SYNOPSIS\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n \t   [-z | --null]\n \t   [-c | --count] [--all-match]\n+\t   [--max-depth <depth>]\n \t   [--color | --no-color]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-f <file>] [-e] <pattern>\n@@ -47,6 +48,10 @@ OPTIONS\n -I::\n \tDon't match the pattern in binary files.\n \n+--max-depth <depth>::\n+\tFor each pathspec given on command line, descend at most <depth>\n+\tlevels of directories. Negative value means no limit.\n+\n -w::\n --word-regexp::\n \tMatch the pattern only at word boundary (either begin at the\ndiff --git a/builtin-grep.c b/builtin-grep.c\nindex f477659..1a9d6a4 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -53,25 +53,57 @@ static int grep_config(const char *var, const char *value, void *cb)\n }\n \n /*\n+ * Return non-zero if max_depth is negative or path has no more then max_depth\n+ * slashes.\n+ */\n+static inline int accept_subdir(const char *path, int max_depth)\n+{\n+\tif (max_depth < 0)\n+\t\treturn 1;\n+\n+\twhile ((path = strchr(path, '/')) != NULL) {\n+\t\tmax_depth--;\n+\t\tif (max_depth < 0)\n+\t\t\treturn 0;\n+\t\tpath++;\n+\t}\n+\treturn 1;\n+}\n+\n+/*\n+ * Return non-zero if name is a subdirectory of match and is not too deep.\n+ */\n+static int is_subdir(const char *name, int namelen,\n+\t\tconst char *match, int matchlen, int max_depth)\n+{\n+\tif (matchlen > namelen || strncmp(name, match, matchlen))\n+\t\treturn 0;\n+\n+\tif (name[matchlen] == '\\0') /* exact match */\n+\t\treturn 1;\n+\n+\tif (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')\n+\t\treturn accept_subdir(name + matchlen + 1, max_depth);\n+\n+\treturn 0;\n+}\n+\n+/*\n  * git grep pathspecs are somewhat different from diff-tree pathspecs;\n  * pathname wildcards are allowed.\n  */\n-static int pathspec_matches(const char **paths, const char *name)\n+static int pathspec_matches(const char **paths, const char *name, int max_depth)\n {\n \tint namelen, i;\n \tif (!paths || !*paths)\n-\t\treturn 1;\n+\t\treturn accept_subdir(name, max_depth);\n \tnamelen = strlen(name);\n \tfor (i = 0; paths[i]; i++) {\n \t\tconst char *match = paths[i];\n \t\tint matchlen = strlen(match);\n \t\tconst char *cp, *meta;\n \n-\t\tif (!matchlen ||\n-\t\t    ((matchlen <= namelen) &&\n-\t\t     !strncmp(name, match, matchlen) &&\n-\t\t     (match[matchlen-1] == '/' ||\n-\t\t      name[matchlen] == '\\0' || name[matchlen] == '/')))\n+\t\tif (is_subdir(name, namelen, match, matchlen, max_depth))\n \t\t\treturn 1;\n \t\tif (!fnmatch(match, name, 0))\n \t\t\treturn 1;\n@@ -421,7 +453,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)\n \t\tint kept;\n \t\tif (!S_ISREG(ce->ce_mode))\n \t\t\tcontinue;\n-\t\tif (!pathspec_matches(paths, ce->name))\n+\t\tif (!pathspec_matches(paths, ce->name, opt->max_depth))\n \t\t\tcontinue;\n \t\tname = ce->name;\n \t\tif (name[0] == '-') {\n@@ -478,7 +510,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,\n \t\tstruct cache_entry *ce = active_cache[nr];\n \t\tif (!S_ISREG(ce->ce_mode))\n \t\t\tcontinue;\n-\t\tif (!pathspec_matches(paths, ce->name))\n+\t\tif (!pathspec_matches(paths, ce->name, opt->max_depth))\n \t\t\tcontinue;\n \t\t/*\n \t\t * If CE_VALID is on, we assume worktree file and its cache entry\n@@ -538,7 +570,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,\n \t\t\tstrbuf_addch(&pathbuf, '/');\n \n \t\tdown = pathbuf.buf + tn_len;\n-\t\tif (!pathspec_matches(paths, down))\n+\t\tif (!pathspec_matches(paths, down, opt->max_depth))\n \t\t\t;\n \t\telse if (S_ISREG(entry.mode))\n \t\t\thit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);\n@@ -692,6 +724,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_SET_INT('I', NULL, &opt.binary,\n \t\t\t\"don't match patterns in binary files\",\n \t\t\tGREP_BINARY_NOMATCH),\n+\t\t{ OPTION_INTEGER, 0, \"max-depth\", &opt.max_depth, \"depth\",\n+\t\t\t\"descend at most <depth> levels\", PARSE_OPT_NONEG,\n+\t\t\tNULL, 1 },\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_BIT('E', \"extended-regexp\", &opt.regflags,\n \t\t\t\"use extended POSIX regular expressions\", REG_EXTENDED),\n@@ -768,6 +803,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \topt.pathname = 1;\n \topt.pattern_tail = &opt.pattern_list;\n \topt.regflags = REG_NEWLINE;\n+\topt.max_depth = -1;\n \n \tstrcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);\n \topt.color = -1;\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 887731e..fb05c48 100755\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1036,6 +1036,7 @@ _git_grep ()\n \t\t\t--extended-regexp --basic-regexp --fixed-strings\n \t\t\t--files-with-matches --name-only\n \t\t\t--files-without-match\n+\t\t\t--max-depth\n \t\t\t--count\n \t\t\t--and --or --not --all-match\n \t\t\t\"\ndiff --git a/grep.h b/grep.h\nindex f00db0e..28e6b2a 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -79,6 +79,7 @@ struct grep_opt {\n \tint pathname;\n \tint null_following_name;\n \tint color;\n+\tint max_depth;\n \tint funcname;\n \tchar color_match[COLOR_MAXLEN];\n \tconst char *color_external;\ndiff --git a/t/t7002-grep.sh b/t/t7002-grep.sh\nindex b13aa7e..b4709e2 100755\n--- a/t/t7002-grep.sh\n+++ b/t/t7002-grep.sh\n@@ -25,13 +25,17 @@ test_expect_success setup '\n \t\techo foo mmap bar_mmap\n \t\techo foo_mmap bar mmap baz\n \t} >file &&\n+\techo vvv >v &&\n \techo ww w >w &&\n \techo x x xx x >x &&\n \techo y yy >y &&\n \techo zzz > z &&\n \tmkdir t &&\n \techo test >t/t &&\n-\tgit add file w x y z t/t hello.c &&\n+\techo vvv >t/v &&\n+\tmkdir t/a &&\n+\techo vvv >t/a/v &&\n+\tgit add . &&\n \ttest_tick &&\n \tgit commit -m initial\n '\n@@ -132,6 +136,51 @@ do\n \t\t! git grep -c test $H | grep /dev/null\n         '\n \n+\ttest_expect_success \"grep --max-depth -1 $L\" '\n+\t\t{\n+\t\t\techo ${HC}t/a/v:1:vvv\n+\t\t\techo ${HC}t/v:1:vvv\n+\t\t\techo ${HC}v:1:vvv\n+\t\t} >expected &&\n+\t\tgit grep --max-depth -1 -n -e vvv $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success \"grep --max-depth 0 $L\" '\n+\t\t{\n+\t\t\techo ${HC}v:1:vvv\n+\t\t} >expected &&\n+\t\tgit grep --max-depth 0 -n -e vvv $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success \"grep --max-depth 0 -- '*' $L\" '\n+\t\t{\n+\t\t\techo ${HC}t/a/v:1:vvv\n+\t\t\techo ${HC}t/v:1:vvv\n+\t\t\techo ${HC}v:1:vvv\n+\t\t} >expected &&\n+\t\tgit grep --max-depth 0 -n -e vvv $H -- \"*\" >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success \"grep --max-depth 1 $L\" '\n+\t\t{\n+\t\t\techo ${HC}t/v:1:vvv\n+\t\t\techo ${HC}v:1:vvv\n+\t\t} >expected &&\n+\t\tgit grep --max-depth 1 -n -e vvv $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success \"grep --max-depth 0 -- t $L\" '\n+\t\t{\n+\t\t\techo ${HC}t/v:1:vvv\n+\t\t} >expected &&\n+\t\tgit grep --max-depth 0 -n -e vvv $H -- t >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n done\n \n cat >expected <<EOF\n-- \n1.6.3.3\n"},{"id":"118478","messageId":"7v1vo8k15w.fsf@alter.siamese.dyndns.org","threadId":"20196","inReplyTo":"1248285135-9033-1-git-send-email-michal.kiedrowicz@gmail.com","subject":"Re: [PATCH v4] grep: Add --max-depth option.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-22T18:44:43Z","receivedAt":"2009-07-22T18:44:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:\n\n> I've merged count_chars() into accept_subdir(), changed OPT_INTEGER to\n> OPTION_INTEGER, which allowed me to set argh to \"<depth>\", to be more\n> consistent with git-grep.txt, and revised documentation.\n\nThanks.  Will queue.\n"},{"id":"118557","messageId":"4A67FF15.7080703@gmail.com","threadId":"20196","inReplyTo":"1248285135-9033-1-git-send-email-michal.kiedrowicz@gmail.com","subject":"Re: [PATCH v4] grep: Add --max-depth option.","fromName":"Stephen Boyd","fromEmail":"bebarino@gmail.com","sentAt":"2009-07-23T06:11:33Z","receivedAt":"2009-07-23T06:11:33Z","isPatch":true,"sender":{"key":"bebarino@gmail.com","avatar":"https://avatars.githubusercontent.com/u/38832?v=4"},"body":"This looks good now. Just one minor point but I don't think it requires\na resend.\n\nMichał Kiedrowicz wrote:\n>\n> +\t\t{ OPTION_INTEGER, 0, \"max-depth\", &opt.max_depth, \"depth\",\n> +\t\t\t\"descend at most <depth> levels\", PARSE_OPT_NONEG,\n> +\t\t\tNULL, 1 },\n\nThis NULL and 1 are not needed unless you're using PARSE_OPT_OPTARG. I\nthink this last line can be safely removed.\n"}]}