{"thread":{"id":"20089","subject":"[PATCH/RFC v2] grep: Add --[no-]recurse options.","startedAt":"2009-07-11T21:20:08Z","lastAt":"2009-07-12T09:39:50Z","messageCount":2,"participants":["Michał Kiedrowicz","René Scharfe"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"117845","messageId":"1247347208-2624-1-git-send-email-michal.kiedrowicz@gmail.com","threadId":"20089","inReplyTo":null,"subject":"[PATCH/RFC v2] grep: Add --[no-]recurse options.","fromName":"Michał Kiedrowicz","fromEmail":"michal.kiedrowicz@gmail.com","sentAt":"2009-07-11T21:20:08Z","receivedAt":"2009-07-11T21:20:08Z","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 I want\nto 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 --no-recurse and (for symmetry) --recurse\noptions to git-grep. If --no-recurse is set, git-grep does not descend\nto subdirectories. When --recurse option is set, directories are\nsearched recursively (this is the default behavior).\n\nIf path specified on command line contains wildcards, option --no-recurse\nmakes no sense, i.e.\n\n$ git grep -l --no-recurse 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---\n\nChanges from previous version:\n * Renamed '--directories=action' to '--[no-]recurse', because my\n   implementation is not compatible with GNU. Also parsing this option was\n   simplified.\n * Moved 'int recurse' into 'struct grep_opt'.\n * Fixed merge conflict with 'master'.\n * Added bash-completion.\n\n Documentation/git-grep.txt             |    7 ++++++\n builtin-grep.c                         |   27 +++++++++++++++++-------\n contrib/completion/git-completion.bash |    1 +\n grep.h                                 |    1 +\n t/t7002-grep.sh                        |   34 +++++++++++++++++++++++++++++++-\n 5 files changed, 61 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex b753c9d..1766d33 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   [--recurse | --no-recurse]\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,12 @@ OPTIONS\n -I::\n \tDon't match the pattern in binary files.\n \n+--recurse::\n+\tProcess directories recursively. This is the default.\n+\n+--no-recurse::\n+\tDo not descend to subdirectories.\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..28e980a 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -52,27 +52,34 @@ static int grep_config(const char *var, const char *value, void *cb)\n \treturn git_color_default_config(var, value, cb);\n }\n \n+static inline int accept_subdir(const char *path, int recurse)\n+{\n+\treturn recurse || !strchr(path, '/');\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 recurse)\n {\n \tint namelen, i;\n \tif (!paths || !*paths)\n-\t\treturn 1;\n+\t\treturn accept_subdir(name, recurse);\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\tif ((!matchlen && accept_subdir(name, recurse)) ||\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\t     (name[matchlen] == '\\0' ||\n+\t\t       ((match[matchlen-1] == '/'|| name[matchlen] == '/') &&\n+\t\t\taccept_subdir(name + matchlen + 1, recurse))))) {\n \t\t\treturn 1;\n+\t\t}\n \t\tif (!fnmatch(match, name, 0))\n \t\t\treturn 1;\n \t\tif (name[namelen-1] != '/')\n@@ -421,7 +428,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->recurse))\n \t\t\tcontinue;\n \t\tname = ce->name;\n \t\tif (name[0] == '-') {\n@@ -478,7 +485,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->recurse))\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 +545,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->recurse))\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 +699,8 @@ 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\tOPT_BOOLEAN(0, \"recurse\", &opt.recurse,\n+\t\t\t\t\"search directories recursively\"),\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@@ -775,6 +784,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (opt.color == -1)\n \t\topt.color = git_use_color_default;\n \n+\topt.recurse = 1;\n+\n \t/*\n \t * If there is no -- then the paths must exist in the working\n \t * tree.  If there is no explicit pattern specified with -e or\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 9c48864..5eec3cc 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--recurse --no-recurse\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..6badf51 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 recurse;\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..0633673 100755\n--- a/t/t7002-grep.sh\n+++ b/t/t7002-grep.sh\n@@ -31,7 +31,9 @@ test_expect_success setup '\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+\tmkdir t/a &&\n+\techo aa aa aa aa >t/a/a &&\n+\tgit add file w x y z t/t t/a/a hello.c &&\n \ttest_tick &&\n \tgit commit -m initial\n '\n@@ -132,6 +134,36 @@ do\n \t\t! git grep -c test $H | grep /dev/null\n         '\n \n+\ttest_expect_success \"grep --recurse $L\" '\n+\t\techo \"${HC}t/t:1:test\" >expected &&\n+\t\tgit grep --recurse -n -e test $H >actual &&\n+\t\tdiff expected actual\n+\t'\n+\n+\ttest_expect_success \"grep --no-recurse $L\" '\n+\t\t: >expected &&\n+\t\tif git grep --no-recurse -e test $H >actual\n+\t\tthen\n+\t\t\techo should not have matched\n+\t\t\tcat actual\n+\t\t\tfalse\n+\t\telse\n+\t\t\tdiff expected actual\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"grep --no-recurse $L -- t\" '\n+\t\t: >expected &&\n+\t\tif git grep --no-recurse -e aa $H -- t >actual\n+\t\tthen\n+\t\t\techo should not have matched\n+\t\t\tcat actual\n+\t\t\tfalse\n+\t\telse\n+\t\t\tdiff expected actual\n+\t\tfi\n+\t'\n+\n done\n \n cat >expected <<EOF\n-- \n1.6.3.3\n"},{"id":"117852","messageId":"4A59AF66.8030105@lsrfire.ath.cx","threadId":"20089","inReplyTo":"1247347208-2624-1-git-send-email-michal.kiedrowicz@gmail.com","subject":"Re: [PATCH/RFC v2] grep: Add --[no-]recurse options.","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2009-07-12T09:39:50Z","receivedAt":"2009-07-12T09:39:50Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Michał Kiedrowicz schrieb:\n> Sometimes it is useful to grep directories non-recursive. E.g. if I want\n> to look for all files in main directory, but not in any subdirectory.\n> Or in Documentation/, but not in Documentation/technical/ and so on.\n> \n> This patch adds support for --no-recurse and (for symmetry) --recurse\n> options to git-grep. If --no-recurse is set, git-grep does not descend\n> to subdirectories. When --recurse option is set, directories are\n> searched recursively (this is the default behavior).\n> \n> If path specified on command line contains wildcards, option --no-recurse\n> makes no sense, i.e.\n> \n> $ git grep -l --no-recurse GNU -- 'contrib/*'\n> \n> (note the quotes) will search all files in contrib/, even in\n> subdirectories, because '*' matches all files.\n>\n> Documentation updates, bash-completion and simple test cases are also\n> provided.\n> \n> Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>\n> ---\n> \n> Changes from previous version:\n>  * Renamed '--directories=action' to '--[no-]recurse', because my\n>    implementation is not compatible with GNU. Also parsing this option was\n>    simplified.\n\nOK.  Yet another idea: if we can't copy an option from grep, perhaps we\ncan steal it from another program?  Would find's -maxdepth option fit\nthe bill?  I imagine it git grep --max-depth=<n> counting the slashes in\npath specs and files and rejecting those files whose count is higher\nthan the one of its (otherwise matching) pattern plus n.\n\n> diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh\n> index b13aa7e..0633673 100755\n> --- a/t/t7002-grep.sh\n> +++ b/t/t7002-grep.sh\n> @@ -31,7 +31,9 @@ test_expect_success setup '\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> +\tmkdir t/a &&\n> +\techo aa aa aa aa >t/a/a &&\n> +\tgit add file w x y z t/t t/a/a hello.c &&\n>  \ttest_tick &&\n>  \tgit commit -m initial\n>  '\n> @@ -132,6 +134,36 @@ do\n>  \t\t! git grep -c test $H | grep /dev/null\n>          '\n>  \n> +\ttest_expect_success \"grep --recurse $L\" '\n> +\t\techo \"${HC}t/t:1:test\" >expected &&\n> +\t\tgit grep --recurse -n -e test $H >actual &&\n> +\t\tdiff expected actual\n> +\t'\n\nUse test_cmp instead of diff.\n\n> +\n> +\ttest_expect_success \"grep --no-recurse $L\" '\n> +\t\t: >expected &&\n> +\t\tif git grep --no-recurse -e test $H >actual\n> +\t\tthen\n> +\t\t\techo should not have matched\n> +\t\t\tcat actual\n> +\t\t\tfalse\n> +\t\telse\n> +\t\t\tdiff expected actual\n> +\t\tfi\n> +\t'\n\nHmm, if git crashed and only wrote to stderr, this test would pass\n(hint: try this with git's master, that doesn't know this option).\nBetter to devise a test in which the tested command still has to print\nsomething (a restricted set of results in this case).\n\n> +\n> +\ttest_expect_success \"grep --no-recurse $L -- t\" '\n> +\t\t: >expected &&\n> +\t\tif git grep --no-recurse -e aa $H -- t >actual\n> +\t\tthen\n> +\t\t\techo should not have matched\n> +\t\t\tcat actual\n> +\t\t\tfalse\n> +\t\telse\n> +\t\t\tdiff expected actual\n> +\t\tfi\n> +\t'\n\nSame here.\n"}]}