{"thread":{"id":"25218","subject":"[RFC/PATCH] Add --exclude-dir option to git grep","startedAt":"2010-09-24T04:26:14Z","lastAt":"2010-09-27T04:53:36Z","messageCount":7,"participants":["David Ripton","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"151422","messageId":"20100924042614.GA25944@nulllenny.dreamhost.com","threadId":"25218","inReplyTo":null,"subject":"[RFC/PATCH] Add --exclude-dir option to git grep","fromName":"David Ripton","fromEmail":"dripton@ripton.net","sentAt":"2010-09-24T04:26:14Z","receivedAt":"2010-09-24T04:26:14Z","isPatch":true,"sender":{"key":"dripton@ripton.net","avatar":"https://avatars.githubusercontent.com/u/153528?v=4"},"body":"It works much like the same option in recent versions of GNU grep.\nAny directory name which matches the option will not be searched.\n\nFor example, \"git grep --exclude-dir Documentation malloc\"\n\nSigned-off-by: David Ripton <dripton@ripton.net>\n---\n builtin/grep.c |  110 ++++++++++++++++++++++++++++++++++++++++++++++++++------\n grep.h         |    2 +\n 2 files changed, 101 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex da32f3d..b22a0f2 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -333,15 +333,62 @@ static int grep_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n\n+/* Return a sorted string_list of all possible directories within path.\n+ *\n+ * e.g. if path is \"foo/bar/baz\", then return a string_list with:\n+ *                 \"bar\"\n+ *                 \"bar/baz\"\n+ *                 \"baz\"\n+ *                 \"foo\"\n+ *                 \"foo/bar\"\n+ *                 \"foo/bar/baz\"\n+ */\n+static struct string_list subdirs(const char *path)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\t/* Make a copy so we can chop off the end. */\n+\tchar *path2 = strdup(path);\n+\t/* A pointer that advances along path2 */\n+\tchar *path3;\n+\tint again = 0;\n+\tdo {\n+\t\tagain = 0;\n+\t\tstring_list_append(&list, path2);\n+\t\tpath3 = path2;\n+\t\twhile ((path3 = strchr(path3, '/')) != NULL) {\n+\t\t\tpath3++;\n+\t\t\tstring_list_append(&list, path3);\n+\t\t}\n+\t\tpath3 = path2;\n+\t\tif ((path3 = strrchr(path3, '/')) != NULL) {\n+\t\t\t*path3 = '\\0';\n+\t\t\tagain = 1;\n+\t\t}\n+\t} while (again);\n+\tfree(path2);\n+\tsort_string_list(&list);\n+\treturn list;\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 int accept_subdir(const char *path, int max_depth)\n+static int accept_subdir(const char *path, int max_depth,\n+\t\t\t\tstruct string_list exclude_dir_list)\n {\n+\tstruct string_list subdir_list = subdirs(path);\n+\tint i;\n+\tfor (i = 0; i < subdir_list.nr; i++) {\n+\t\tif (string_list_has_string(&exclude_dir_list, subdir_list.items[i].string)) {\n+\t\t\tstring_list_clear(&subdir_list, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\tstring_list_clear(&subdir_list, 0);\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@@ -355,7 +402,8 @@ static int accept_subdir(const char *path, int max_depth)\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+\t\tconst char *match, int matchlen, int max_depth,\n+\t\tstruct string_list exclude_dir_list)\n {\n \tif (matchlen > namelen || strncmp(name, match, matchlen))\n \t\treturn 0;\n@@ -364,7 +412,8 @@ static int is_subdir(const char *name, int namelen,\n \t\treturn 1;\n\n \tif (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')\n-\t\treturn accept_subdir(name + matchlen + 1, max_depth);\n+\t\treturn accept_subdir(name + matchlen + 1, max_depth,\n+\t\t\t\t\texclude_dir_list);\n\n \treturn 0;\n }\n@@ -373,18 +422,21 @@ static int is_subdir(const char *name, int namelen,\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, int max_depth)\n+static int pathspec_matches(const char **paths, const char *name,\n+\t\t\t\tint max_depth,\n+\t\t\t\tstruct string_list exclude_dir_list)\n {\n \tint namelen, i;\n \tif (!paths || !*paths)\n-\t\treturn accept_subdir(name, max_depth);\n+\t\treturn accept_subdir(name, max_depth, exclude_dir_list);\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 (is_subdir(name, namelen, match, matchlen, max_depth))\n+\t\tif (is_subdir(name, namelen, match, matchlen, max_depth,\n+\t\t\t\texclude_dir_list))\n \t\t\treturn 1;\n \t\tif (!fnmatch(match, name, 0))\n \t\t\treturn 1;\n@@ -595,7 +647,8 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, ce->name, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\n \t\t\tcontinue;\n \t\t/*\n \t\t * If CE_VALID is on, we assume worktree file and its cache entry\n@@ -656,7 +709,8 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, down, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\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@@ -722,7 +776,8 @@ static int grep_objects(struct grep_opt *opt, const char **paths,\n \treturn hit;\n }\n\n-static int grep_directory(struct grep_opt *opt, const char **paths)\n+static int grep_directory(struct grep_opt *opt, const char **paths,\n+\t\t\t\tstruct string_list exclude_dir_list)\n {\n \tstruct dir_struct dir;\n \tint i, hit = 0;\n@@ -730,7 +785,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths)\n \tmemset(&dir, 0, sizeof(dir));\n \tsetup_standard_excludes(&dir);\n\n+\tfor (i = 0; i < exclude_dir_list.nr; i++)\n+\t\tadd_exclude(exclude_dir_list.items[i].string, \"\", 0,\n+\t\t\t\tdir.exclude_list);\n+\n \tfill_directory(&dir, paths);\n+\n \tfor (i = 0; i < dir.nr; i++) {\n \t\thit |= grep_file(opt, dir.entries[i]->name);\n \t\tif (hit && opt->status_only)\n@@ -826,6 +886,25 @@ static int help_callback(const struct option *opt, const char *arg, int unset)\n \treturn -1;\n }\n\n+static int exclude_dir_callback(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct string_list *exclude_dir_list = opt->value;\n+\tchar *s1 = (char *)arg;\n+\t/* We do not want leading or trailing slashes. */\n+\twhile (*s1 == '/') {\n+\t\ts1++;\n+\t}\n+\tchar *s2 = strdup(s1);\n+\twhile (*s2 && s2[strlen(s2)-1] == '/') {\n+\t\ts2[strlen(s2)-1] = '\\0';\n+\t}\n+\tstring_list_append(exclude_dir_list, s2);\n+\tfree(s2);\n+\treturn 0;\n+}\n+\n+\n int cmd_grep(int argc, const char **argv, const char *prefix)\n {\n \tint hit = 0;\n@@ -837,6 +916,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tstruct object_array list = OBJECT_ARRAY_INIT;\n \tconst char **paths = NULL;\n \tstruct string_list path_list = STRING_LIST_INIT_NODUP;\n+\tstruct string_list exclude_dir_list = STRING_LIST_INIT_DUP;\n \tint i;\n \tint dummy;\n \tint use_index = 1;\n@@ -920,6 +1000,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN(0, \"all-match\", &opt.all_match,\n \t\t\t\"show only matches from files that match all patterns\"),\n \t\tOPT_GROUP(\"\"),\n+\t\t{ OPTION_CALLBACK, 0, \"exclude-dir\", &exclude_dir_list,\n+\t\t  \"pattern\", \"exclude <pattern>\", PARSE_OPT_NONEG,\n+\t\t  exclude_dir_callback },\n+\t\tOPT_GROUP(\"\"),\n \t\t{ OPTION_STRING, 'O', \"open-files-in-pager\", &show_in_pager,\n \t\t\t\"pager\", \"show matching files in the pager\",\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },\n@@ -974,6 +1058,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\t     PARSE_OPT_STOP_AT_NON_OPTION |\n \t\t\t     PARSE_OPT_NO_INTERNAL_HELP);\n\n+\tsort_string_list(&exclude_dir_list);\n+\topt.exclude_dir_list = exclude_dir_list;\n+\n \tif (use_index && !startup_info->have_repository)\n \t\t/* die the same way as if we did it at the beginning */\n \t\tsetup_git_directory();\n@@ -1093,7 +1180,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"--cached cannot be used with --no-index.\");\n \t\tif (list.nr)\n \t\t\tdie(\"--no-index cannot be used with revs.\");\n-\t\thit = grep_directory(&opt, paths);\n+\t\thit = grep_directory(&opt, paths, exclude_dir_list);\n \t} else if (!list.nr) {\n \t\tif (!cached)\n \t\t\tsetup_work_tree();\n@@ -1110,5 +1197,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (hit && show_in_pager)\n \t\trun_pager(&opt, prefix);\n \tfree_grep_patterns(&opt);\n+\tstring_list_clear(&exclude_dir_list, 0);\n \treturn !hit;\n }\ndiff --git a/grep.h b/grep.h\nindex efa8cff..0400611 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -1,6 +1,7 @@\n #ifndef GREP_H\n #define GREP_H\n #include \"color.h\"\n+#include \"string-list.h\"\n\n enum grep_pat_token {\n \tGREP_PATTERN,\n@@ -99,6 +100,7 @@ struct grep_opt {\n \tunsigned post_context;\n \tunsigned last_shown;\n \tint show_hunk_mark;\n+\tstruct string_list exclude_dir_list;\n \tvoid *priv;\n\n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n\n-- \nDavid Ripton    dripton@ripton.net\n"},{"id":"151542","messageId":"7v1v8iq3tu.fsf@alter.siamese.dyndns.org","threadId":"25218","inReplyTo":"20100924042614.GA25944@nulllenny.dreamhost.com","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-09-24T20:33:33Z","receivedAt":"2010-09-24T20:33:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Ripton <dripton@ripton.net> writes:\n\n> It works much like the same option in recent versions of GNU grep.\n> Any directory name which matches the option will not be searched.\n>\n> For example, \"git grep --exclude-dir Documentation malloc\"\n>\n> Signed-off-by: David Ripton <dripton@ripton.net>\n\nThanks.\n\n> +/* Return a sorted string_list of all possible directories within path.\n> + *\n> + * e.g. if path is \"foo/bar/baz\", then return a string_list with:\n> + *                 \"bar\"\n> + *                 \"bar/baz\"\n> + *                 \"baz\"\n> + *                 \"foo\"\n> + *                 \"foo/bar\"\n> + *                 \"foo/bar/baz\"\n> + */\n> +static struct string_list subdirs(const char *path)\n> +{\n> +...\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 int accept_subdir(const char *path, int max_depth)\n> +static int accept_subdir(const char *path, int max_depth,\n> +\t\t\t\tstruct string_list exclude_dir_list)\n>  {\n> +\tstruct string_list subdir_list = subdirs(path);\n\nDo you need to run this every time we visit a new directory, expanding\ndirectory components over and over?\n\nIt is not like we are jumping around directory hierarchies, visiting\n\"foo/bar\" and then \"xyzzy\" and then \"foo/baz\", but rather we visit\ndirectories in a nicer order (i.e. after leaving \"foo/bar\" but before\njumping to \"xyzzy\", we would visit \"foo/baz\"), don't we?\n\nFor example, if we are about to visit \"foo/bar/baz\", that would mean we\nwere in \"foo/bar\" and already checked that our exclude list is Ok with\neither \"foo\", \"foo/bar\" or \"bar\"; shouldn't we be skipping the test for\nthese three expansions at least?  IOW, when checking against the exclude\nlist, shouldn't we be testing with \"baz\", \"bar/baz\" and \"foo/bar/baz\" and\nnothing else?\n\n> +\tint i;\n> +\tfor (i = 0; i < subdir_list.nr; i++) {\n> +\t\tif (string_list_has_string(&exclude_dir_list, subdir_list.items[i].string)) {\n> +\t\t\tstring_list_clear(&subdir_list, 0);\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t}\n> +\tstring_list_clear(&subdir_list, 0);\n> +\n>  \tif (max_depth < 0)\n>  \t\treturn 1;\n\nIsn't this original check much cheaper than the new test based on many\ncomparisons and should be at the beginning of the function?\n\n> @@ -826,6 +886,25 @@ static int help_callback(const struct option *opt, const char *arg, int unset)\n>  \treturn -1;\n>  }\n>\n> +static int exclude_dir_callback(const struct option *opt, const char *arg,\n> +\t\t\t\tint unset)\n> +{\n> +\tstruct string_list *exclude_dir_list = opt->value;\n> +\tchar *s1 = (char *)arg;\n\nWhat is this cast for?\n\n> +\t/* We do not want leading or trailing slashes. */\n> +\twhile (*s1 == '/') {\n> +\t\ts1++;\n> +\t}\n\nCan the result of this loop become an empty string, and what happens to\nthe rest of the logic when it happens?\n\n> +\tchar *s2 = strdup(s1);\n\ndecl-after-statement.  Use xstrdup().\n\n> +\twhile (*s2 && s2[strlen(s2)-1] == '/') {\n> +\t\ts2[strlen(s2)-1] = '\\0';\n> +\t}\n\nDon't scan s2 repeatedly to find its end by calling strlen(s2) on it.\nFind its length once, and scan backwards from there yourself.\n"},{"id":"151596","messageId":"4C9D596C.4060906@ripton.net","threadId":"25218","inReplyTo":"7v1v8iq3tu.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"David Ripton","fromEmail":"dripton@ripton.net","sentAt":"2010-09-25T02:07:40Z","receivedAt":"2010-09-25T02:07:40Z","isPatch":true,"sender":{"key":"dripton@ripton.net","avatar":"https://avatars.githubusercontent.com/u/153528?v=4"},"body":"On 09/24/10 16:33, Junio C Hamano wrote:\n\n> Thanks.\n\nThank you for the code review.\n\n> Do you need to run this every time we visit a new directory, expanding\n> directory components over and over?\n>\n> It is not like we are jumping around directory hierarchies, visiting\n> \"foo/bar\" and then \"xyzzy\" and then \"foo/baz\", but rather we visit\n> directories in a nicer order (i.e. after leaving \"foo/bar\" but before\n> jumping to \"xyzzy\", we would visit \"foo/baz\"), don't we?\n\nI agree that there's room for optimization here.\n\n>>   \tif (max_depth<  0)\n>>   \t\treturn 1;\n>\n> Isn't this original check much cheaper than the new test based on many\n> comparisons and should be at the beginning of the function?\n\nYes.\n\n>> @@ -826,6 +886,25 @@ static int help_callback(const struct option *opt, const char *arg, int unset)\n>>   \treturn -1;\n>>   }\n>>\n>> +static int exclude_dir_callback(const struct option *opt, const char *arg,\n>> +\t\t\t\tint unset)\n>> +{\n>> +\tstruct string_list *exclude_dir_list = opt->value;\n>> +\tchar *s1 = (char *)arg;\n>\n> What is this cast for?\n\nIt avoids:\n\n\"builtin/grep.c:893: warning: initialization discards qualifiers from \npointer target type\"\n\n>> +\t/* We do not want leading or trailing slashes. */\n>> +\twhile (*s1 == '/') {\n>> +\t\ts1++;\n>> +\t}\n>\n> Can the result of this loop become an empty string, and what happens to\n> the rest of the logic when it happens?\n\nIf the string is just forward slashes, then it will become an empty \nstring, which will strdup successfully, and then that particular \n--exclude-dir will have no effect.  Just tested that case and did not \nfind a bug.\n\n>> +\tchar *s2 = strdup(s1);\n>\n> decl-after-statement.\n\nOops.\n\nSadly, \"gcc -Wall -std=c89\" does not warn for this.  (\"-pedantic\" does.)\n\n> Use xstrdup().\n\nOkay.\n\n>> +\twhile (*s2&&  s2[strlen(s2)-1] == '/') {\n>> +\t\ts2[strlen(s2)-1] = '\\0';\n>> +\t}\n>\n> Don't scan s2 repeatedly to find its end by calling strlen(s2) on it.\n> Find its length once, and scan backwards from there yourself.\n\nOkay.  I'll try to send out a revised version of this patch soon.\n\n-- \nDavid Ripton    dripton@ripton.net\n"},{"id":"151602","messageId":"20100925033530.GA21483@nulllenny.dreamhost.com","threadId":"25218","inReplyTo":"7v1v8iq3tu.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"David Ripton","fromEmail":"dripton@ripton.net","sentAt":"2010-09-25T03:35:42Z","receivedAt":"2010-09-25T03:35:42Z","isPatch":true,"sender":{"key":"dripton@ripton.net","avatar":"https://avatars.githubusercontent.com/u/153528?v=4"},"body":"It works much like the same option in recent versions of GNU grep.\nAny directory name which matches the option will not be searched.\n\nFor example, \"git grep --exclude-dir Documentation malloc\"\n\nSigned-off-by: David Ripton <dripton@ripton.net>\n---\nVersion 2 of this patch, following Junio's comments:\nstrdup() -> xstrdup()\nFixed a declaration after code.\nRemoved basenames from subdirs()\nDo not call subdirs() at all if exclude_dir_list is empty.\n\nUnfortunately the other suggested optimization, moving the original test for\nmax_depth < 0 in accept_subdir to the top, turned out to be unsafe.  And\nsimplifying subdirs() to only deal with the last subdirectory rather than the\nwhole path makes it difficult to exclude a multi-part directory like\n\"Documentation/technical\".  But now we totally skip the subdirs() call when\nexclude_dir_list is empty, so at least the cost is only born by those \nactually using this option.\n\n builtin/grep.c |  130 +++++++++++++++++++++++++++++++++++++++++++++++++------\n grep.h         |    2 +\n 2 files changed, 117 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex da32f3d..220a7db 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -333,15 +333,68 @@ static int grep_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n+/* Return a sorted string_list of all possible directories within path.\n+ *\n+ * e.g. if path is \"foo/bar/baz\", then return a string_list with:\n+ *                 \"bar\"\n+ *                 \"foo\"\n+ *                 \"foo/bar\"\n+ *\n+ *  (We do not need to return baz because the paths we receive always\n+ *  end with a file not a directory.)\n+ */\n+static struct string_list subdirs(const char *path)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\t/* Make a copy so we can chop off the end. */\n+\tchar *path2 = xstrdup(path);\n+\t/* A pointer that advances along path2 */\n+\tchar *path3 = path2;\n+\t/* Chop off the basename portion. */\n+\tif ((path3 = strrchr(path3, '/')) != NULL)\n+\t\t*path3 = '\\0';\n+\tint again = 0;\n+\tdo {\n+\t\tagain = 0;\n+\t\tstring_list_append(&list, path2);\n+\t\tpath3 = path2;\n+\t\twhile ((path3 = strchr(path3, '/')) != NULL) {\n+\t\t\tpath3++;\n+\t\t\tstring_list_append(&list, path3);\n+\t\t}\n+\t\tpath3 = path2;\n+\t\tif ((path3 = strrchr(path3, '/')) != NULL) {\n+\t\t\t*path3 = '\\0';\n+\t\t\tagain = 1;\n+\t\t}\n+\t} while (again);\n+\tfree(path2);\n+\tsort_string_list(&list);\n+\treturn list;\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 int accept_subdir(const char *path, int max_depth)\n+static int accept_subdir(const char *path, int max_depth,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n+\tif (exclude_dir_list.nr > 0) {\n+\t\tstruct string_list subdir_list = subdirs(path);\n+\t\tint i;\n+\t\tfor (i = 0; i < subdir_list.nr; i++) {\n+\t\t\tif (string_list_has_string(&exclude_dir_list,\n+\t\t\t\t\tsubdir_list.items[i].string)) {\n+\t\t\t\tstring_list_clear(&subdir_list, 0);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\tstring_list_clear(&subdir_list, 0);\n+\t}\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@@ -355,7 +408,8 @@ static int accept_subdir(const char *path, int max_depth)\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+\t\tconst char *match, int matchlen, int max_depth,\n+\t\tconst struct string_list exclude_dir_list)\n {\n \tif (matchlen > namelen || strncmp(name, match, matchlen))\n \t\treturn 0;\n@@ -364,7 +418,8 @@ static int is_subdir(const char *name, int namelen,\n \t\treturn 1;\n \n \tif (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')\n-\t\treturn accept_subdir(name + matchlen + 1, max_depth);\n+\t\treturn accept_subdir(name + matchlen + 1, max_depth,\n+\t\t\t\t\texclude_dir_list);\n \n \treturn 0;\n }\n@@ -373,18 +428,21 @@ static int is_subdir(const char *name, int namelen,\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, int max_depth)\n+static int pathspec_matches(const char **paths, const char *name,\n+\t\t\t\tint max_depth,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n \tint namelen, i;\n \tif (!paths || !*paths)\n-\t\treturn accept_subdir(name, max_depth);\n+\t\treturn accept_subdir(name, max_depth, exclude_dir_list);\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 (is_subdir(name, namelen, match, matchlen, max_depth))\n+\t\tif (is_subdir(name, namelen, match, matchlen, max_depth,\n+\t\t\t\texclude_dir_list))\n \t\t\treturn 1;\n \t\tif (!fnmatch(match, name, 0))\n \t\t\treturn 1;\n@@ -595,14 +653,17 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, ce->name, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\n \t\t\tcontinue;\n+\n \t\t/*\n-\t\t * If CE_VALID is on, we assume worktree file and its cache entry\n-\t\t * are identical, even if worktree file has been modified, so use\n-\t\t * cache version instead\n+\t\t * If CE_VALID is on, we assume worktree file and its cache\n+\t\t * entry are identical, even if worktree file has been\n+\t\t * modified, so use cache version instead\n \t\t */\n-\t\tif (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {\n+\t\tif (cached || (ce->ce_flags & CE_VALID) ||\n+\t\t\tce_skip_worktree(ce)) {\n \t\t\tif (ce_stage(ce))\n \t\t\t\tcontinue;\n \t\t\thit |= grep_sha1(opt, ce->sha1, ce->name, 0);\n@@ -656,7 +717,8 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, down, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\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@@ -722,7 +784,8 @@ static int grep_objects(struct grep_opt *opt, const char **paths,\n \treturn hit;\n }\n \n-static int grep_directory(struct grep_opt *opt, const char **paths)\n+static int grep_directory(struct grep_opt *opt, const char **paths,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n \tstruct dir_struct dir;\n \tint i, hit = 0;\n@@ -730,7 +793,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths)\n \tmemset(&dir, 0, sizeof(dir));\n \tsetup_standard_excludes(&dir);\n \n+\tfor (i = 0; i < exclude_dir_list.nr; i++)\n+\t\tadd_exclude(exclude_dir_list.items[i].string, \"\", 0,\n+\t\t\t\tdir.exclude_list);\n+\n \tfill_directory(&dir, paths);\n+\n \tfor (i = 0; i < dir.nr; i++) {\n \t\thit |= grep_file(opt, dir.entries[i]->name);\n \t\tif (hit && opt->status_only)\n@@ -826,6 +894,29 @@ static int help_callback(const struct option *opt, const char *arg, int unset)\n \treturn -1;\n }\n \n+static int exclude_dir_callback(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct string_list *exclude_dir_list = opt->value;\n+\tchar *s1 = (char *)arg;\n+\tchar *s2;\n+\tchar *s3;\n+\t/* We do not want leading or trailing slashes. */\n+\twhile (*s1 == '/') {\n+\t\ts1++;\n+\t}\n+\ts2 = xstrdup(s1);\n+\ts3 = s2 + strlen(s2) - 1;\n+\twhile (s3 >= s2 && *s3 == '/') {\n+\t\t*s3 = '\\0';\n+\t\ts3--;\n+\t}\n+\tstring_list_append(exclude_dir_list, s2);\n+\tfree(s2);\n+\treturn 0;\n+}\n+\n+\n int cmd_grep(int argc, const char **argv, const char *prefix)\n {\n \tint hit = 0;\n@@ -837,6 +928,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tstruct object_array list = OBJECT_ARRAY_INIT;\n \tconst char **paths = NULL;\n \tstruct string_list path_list = STRING_LIST_INIT_NODUP;\n+\tstruct string_list exclude_dir_list = STRING_LIST_INIT_DUP;\n \tint i;\n \tint dummy;\n \tint use_index = 1;\n@@ -920,6 +1012,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN(0, \"all-match\", &opt.all_match,\n \t\t\t\"show only matches from files that match all patterns\"),\n \t\tOPT_GROUP(\"\"),\n+\t\t{ OPTION_CALLBACK, 0, \"exclude-dir\", &exclude_dir_list,\n+\t\t  \"pattern\", \"exclude <pattern>\", PARSE_OPT_NONEG,\n+\t\t  exclude_dir_callback },\n+\t\tOPT_GROUP(\"\"),\n \t\t{ OPTION_STRING, 'O', \"open-files-in-pager\", &show_in_pager,\n \t\t\t\"pager\", \"show matching files in the pager\",\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },\n@@ -974,6 +1070,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\t     PARSE_OPT_STOP_AT_NON_OPTION |\n \t\t\t     PARSE_OPT_NO_INTERNAL_HELP);\n \n+\tsort_string_list(&exclude_dir_list);\n+\topt.exclude_dir_list = exclude_dir_list;\n+\n \tif (use_index && !startup_info->have_repository)\n \t\t/* die the same way as if we did it at the beginning */\n \t\tsetup_git_directory();\n@@ -1093,7 +1192,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"--cached cannot be used with --no-index.\");\n \t\tif (list.nr)\n \t\t\tdie(\"--no-index cannot be used with revs.\");\n-\t\thit = grep_directory(&opt, paths);\n+\t\thit = grep_directory(&opt, paths, exclude_dir_list);\n \t} else if (!list.nr) {\n \t\tif (!cached)\n \t\t\tsetup_work_tree();\n@@ -1110,5 +1209,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (hit && show_in_pager)\n \t\trun_pager(&opt, prefix);\n \tfree_grep_patterns(&opt);\n+\tstring_list_clear(&exclude_dir_list, 0);\n \treturn !hit;\n }\ndiff --git a/grep.h b/grep.h\nindex efa8cff..0400611 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -1,6 +1,7 @@\n #ifndef GREP_H\n #define GREP_H\n #include \"color.h\"\n+#include \"string-list.h\"\n \n enum grep_pat_token {\n \tGREP_PATTERN,\n@@ -99,6 +100,7 @@ struct grep_opt {\n \tunsigned post_context;\n \tunsigned last_shown;\n \tint show_hunk_mark;\n+\tstruct string_list exclude_dir_list;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n\n-- \nDavid Ripton    dripton@ripton.net\n"},{"id":"151611","messageId":"7vsk0ynzfl.fsf@alter.siamese.dyndns.org","threadId":"25218","inReplyTo":"4C9D596C.4060906@ripton.net","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-09-25T05:51:26Z","receivedAt":"2010-09-25T05:51:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Ripton <dripton@ripton.net> writes:\n\n>>> +static int exclude_dir_callback(const struct option *opt, const char *arg,\n>>> +\t\t\t\tint unset)\n>>> +{\n>>> +\tstruct string_list *exclude_dir_list = opt->value;\n>>> +\tchar *s1 = (char *)arg;\n>>\n>> What is this cast for?\n>\n> It avoids:\n>\n> \"builtin/grep.c:893: warning: initialization discards qualifiers from\n> pointer target type\"\n\nAnd the reason why s1 cannot be of type \"const char *\" is...?\n"},{"id":"151639","messageId":"20100925131444.GA7142@nulllenny.dreamhost.com","threadId":"25218","inReplyTo":"7vsk0ynzfl.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"David Ripton","fromEmail":"dripton@ripton.net","sentAt":"2010-09-25T13:14:44Z","receivedAt":"2010-09-25T13:14:44Z","isPatch":true,"sender":{"key":"dripton@ripton.net","avatar":"https://avatars.githubusercontent.com/u/153528?v=4"},"body":"It works much like the same option in recent versions of GNU grep.\nAny directory name which matches the option will not be searched.\n\nFor example, \"git grep --exclude-dir Documentation malloc\"\n\nSigned-off-by: David Ripton <dripton@ripton.net>\n---\nThis version makes \"s1\" const, rather than casting away const.\n\n builtin/grep.c |  130 +++++++++++++++++++++++++++++++++++++++++++++++++------\n grep.h         |    2 +\n 2 files changed, 117 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex da32f3d..699b308 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -333,15 +333,68 @@ static int grep_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n+/* Return a sorted string_list of all possible directories within path.\n+ *\n+ * e.g. if path is \"foo/bar/baz\", then return a string_list with:\n+ *                 \"bar\"\n+ *                 \"foo\"\n+ *                 \"foo/bar\"\n+ *\n+ *  (We do not need to return baz because the paths we receive always\n+ *  end with a file not a directory.)\n+ */\n+static struct string_list subdirs(const char *path)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\t/* Make a copy so we can chop off the end. */\n+\tchar *path2 = xstrdup(path);\n+\t/* A pointer that advances along path2 */\n+\tchar *path3 = path2;\n+\t/* Chop off the basename portion. */\n+\tif ((path3 = strrchr(path3, '/')) != NULL)\n+\t\t*path3 = '\\0';\n+\tint again = 0;\n+\tdo {\n+\t\tagain = 0;\n+\t\tstring_list_append(&list, path2);\n+\t\tpath3 = path2;\n+\t\twhile ((path3 = strchr(path3, '/')) != NULL) {\n+\t\t\tpath3++;\n+\t\t\tstring_list_append(&list, path3);\n+\t\t}\n+\t\tpath3 = path2;\n+\t\tif ((path3 = strrchr(path3, '/')) != NULL) {\n+\t\t\t*path3 = '\\0';\n+\t\t\tagain = 1;\n+\t\t}\n+\t} while (again);\n+\tfree(path2);\n+\tsort_string_list(&list);\n+\treturn list;\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 int accept_subdir(const char *path, int max_depth)\n+static int accept_subdir(const char *path, int max_depth,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n+\tif (exclude_dir_list.nr > 0) {\n+\t\tstruct string_list subdir_list = subdirs(path);\n+\t\tint i;\n+\t\tfor (i = 0; i < subdir_list.nr; i++) {\n+\t\t\tif (string_list_has_string(&exclude_dir_list,\n+\t\t\t\t\tsubdir_list.items[i].string)) {\n+\t\t\t\tstring_list_clear(&subdir_list, 0);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t\tstring_list_clear(&subdir_list, 0);\n+\t}\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@@ -355,7 +408,8 @@ static int accept_subdir(const char *path, int max_depth)\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+\t\tconst char *match, int matchlen, int max_depth,\n+\t\tconst struct string_list exclude_dir_list)\n {\n \tif (matchlen > namelen || strncmp(name, match, matchlen))\n \t\treturn 0;\n@@ -364,7 +418,8 @@ static int is_subdir(const char *name, int namelen,\n \t\treturn 1;\n \n \tif (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')\n-\t\treturn accept_subdir(name + matchlen + 1, max_depth);\n+\t\treturn accept_subdir(name + matchlen + 1, max_depth,\n+\t\t\t\t\texclude_dir_list);\n \n \treturn 0;\n }\n@@ -373,18 +428,21 @@ static int is_subdir(const char *name, int namelen,\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, int max_depth)\n+static int pathspec_matches(const char **paths, const char *name,\n+\t\t\t\tint max_depth,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n \tint namelen, i;\n \tif (!paths || !*paths)\n-\t\treturn accept_subdir(name, max_depth);\n+\t\treturn accept_subdir(name, max_depth, exclude_dir_list);\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 (is_subdir(name, namelen, match, matchlen, max_depth))\n+\t\tif (is_subdir(name, namelen, match, matchlen, max_depth,\n+\t\t\t\texclude_dir_list))\n \t\t\treturn 1;\n \t\tif (!fnmatch(match, name, 0))\n \t\t\treturn 1;\n@@ -595,14 +653,17 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, ce->name, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\n \t\t\tcontinue;\n+\n \t\t/*\n-\t\t * If CE_VALID is on, we assume worktree file and its cache entry\n-\t\t * are identical, even if worktree file has been modified, so use\n-\t\t * cache version instead\n+\t\t * If CE_VALID is on, we assume worktree file and its cache\n+\t\t * entry are identical, even if worktree file has been\n+\t\t * modified, so use cache version instead\n \t\t */\n-\t\tif (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {\n+\t\tif (cached || (ce->ce_flags & CE_VALID) ||\n+\t\t\tce_skip_worktree(ce)) {\n \t\t\tif (ce_stage(ce))\n \t\t\t\tcontinue;\n \t\t\thit |= grep_sha1(opt, ce->sha1, ce->name, 0);\n@@ -656,7 +717,8 @@ 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, opt->max_depth))\n+\t\tif (!pathspec_matches(paths, down, opt->max_depth,\n+\t\t\t\t\topt->exclude_dir_list))\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@@ -722,7 +784,8 @@ static int grep_objects(struct grep_opt *opt, const char **paths,\n \treturn hit;\n }\n \n-static int grep_directory(struct grep_opt *opt, const char **paths)\n+static int grep_directory(struct grep_opt *opt, const char **paths,\n+\t\t\t\tconst struct string_list exclude_dir_list)\n {\n \tstruct dir_struct dir;\n \tint i, hit = 0;\n@@ -730,7 +793,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths)\n \tmemset(&dir, 0, sizeof(dir));\n \tsetup_standard_excludes(&dir);\n \n+\tfor (i = 0; i < exclude_dir_list.nr; i++)\n+\t\tadd_exclude(exclude_dir_list.items[i].string, \"\", 0,\n+\t\t\t\tdir.exclude_list);\n+\n \tfill_directory(&dir, paths);\n+\n \tfor (i = 0; i < dir.nr; i++) {\n \t\thit |= grep_file(opt, dir.entries[i]->name);\n \t\tif (hit && opt->status_only)\n@@ -826,6 +894,29 @@ static int help_callback(const struct option *opt, const char *arg, int unset)\n \treturn -1;\n }\n \n+static int exclude_dir_callback(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct string_list *exclude_dir_list = opt->value;\n+\tconst char *s1 = arg;\n+\tchar *s2;\n+\tchar *s3;\n+\t/* We do not want leading or trailing slashes. */\n+\twhile (*s1 == '/') {\n+\t\ts1++;\n+\t}\n+\ts2 = xstrdup(s1);\n+\ts3 = s2 + strlen(s2) - 1;\n+\twhile (s3 >= s2 && *s3 == '/') {\n+\t\t*s3 = '\\0';\n+\t\ts3--;\n+\t}\n+\tstring_list_append(exclude_dir_list, s2);\n+\tfree(s2);\n+\treturn 0;\n+}\n+\n+\n int cmd_grep(int argc, const char **argv, const char *prefix)\n {\n \tint hit = 0;\n@@ -837,6 +928,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tstruct object_array list = OBJECT_ARRAY_INIT;\n \tconst char **paths = NULL;\n \tstruct string_list path_list = STRING_LIST_INIT_NODUP;\n+\tstruct string_list exclude_dir_list = STRING_LIST_INIT_DUP;\n \tint i;\n \tint dummy;\n \tint use_index = 1;\n@@ -920,6 +1012,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN(0, \"all-match\", &opt.all_match,\n \t\t\t\"show only matches from files that match all patterns\"),\n \t\tOPT_GROUP(\"\"),\n+\t\t{ OPTION_CALLBACK, 0, \"exclude-dir\", &exclude_dir_list,\n+\t\t  \"pattern\", \"exclude <pattern>\", PARSE_OPT_NONEG,\n+\t\t  exclude_dir_callback },\n+\t\tOPT_GROUP(\"\"),\n \t\t{ OPTION_STRING, 'O', \"open-files-in-pager\", &show_in_pager,\n \t\t\t\"pager\", \"show matching files in the pager\",\n \t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },\n@@ -974,6 +1070,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\t     PARSE_OPT_STOP_AT_NON_OPTION |\n \t\t\t     PARSE_OPT_NO_INTERNAL_HELP);\n \n+\tsort_string_list(&exclude_dir_list);\n+\topt.exclude_dir_list = exclude_dir_list;\n+\n \tif (use_index && !startup_info->have_repository)\n \t\t/* die the same way as if we did it at the beginning */\n \t\tsetup_git_directory();\n@@ -1093,7 +1192,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"--cached cannot be used with --no-index.\");\n \t\tif (list.nr)\n \t\t\tdie(\"--no-index cannot be used with revs.\");\n-\t\thit = grep_directory(&opt, paths);\n+\t\thit = grep_directory(&opt, paths, exclude_dir_list);\n \t} else if (!list.nr) {\n \t\tif (!cached)\n \t\t\tsetup_work_tree();\n@@ -1110,5 +1209,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (hit && show_in_pager)\n \t\trun_pager(&opt, prefix);\n \tfree_grep_patterns(&opt);\n+\tstring_list_clear(&exclude_dir_list, 0);\n \treturn !hit;\n }\ndiff --git a/grep.h b/grep.h\nindex efa8cff..0400611 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -1,6 +1,7 @@\n #ifndef GREP_H\n #define GREP_H\n #include \"color.h\"\n+#include \"string-list.h\"\n \n enum grep_pat_token {\n \tGREP_PATTERN,\n@@ -99,6 +100,7 @@ struct grep_opt {\n \tunsigned post_context;\n \tunsigned last_shown;\n \tint show_hunk_mark;\n+\tstruct string_list exclude_dir_list;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n\n-- \nDavid Ripton    dripton@ripton.net\n"},{"id":"151786","messageId":"7vzkv33hyn.fsf@alter.siamese.dyndns.org","threadId":"25218","inReplyTo":"20100925033530.GA21483@nulllenny.dreamhost.com","subject":"Re: [RFC/PATCH] Add --exclude-dir option to git grep","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-09-27T04:53:36Z","receivedAt":"2010-09-27T04:53:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Ripton <dripton@ripton.net> writes:\n\n> It works much like the same option in recent versions of GNU grep.\n> Any directory name which matches the option will not be searched.\n>\n> For example, \"git grep --exclude-dir Documentation malloc\"\n>\n> Signed-off-by: David Ripton <dripton@ripton.net>\n> ---\n> Version 2 of this patch, following Junio's comments:\n> strdup() -> xstrdup()\n> Fixed a declaration after code.\n> Removed basenames from subdirs()\n> Do not call subdirs() at all if exclude_dir_list is empty.\n>\n> Unfortunately the other suggested optimization, moving the original test for\n> max_depth < 0 in accept_subdir to the top, turned out to be unsafe.  And\n> simplifying subdirs() to only deal with the last subdirectory rather than the\n> whole path makes it difficult to exclude a multi-part directory like\n> \"Documentation/technical\".\n\nDoesn't the caller have the full path, relative to the root of the working\ntree, at that point?  That is what the \"name\" parameter given to\npathspec_matches() is, and is given to accept_subdir().\n\nIf you are in \"x/y/doc\" and about to visit \"tech\", \"x/y/doc/tech\" is given\nto you in \"name\" to see if it is worth going into that hierarchy.\n\nWhy isn't it enough to check that \"tech\", \"doc/tech\", \"y/doc/tech\", nor\n\"x/y/doc/tech\" appear in the list of excluded patterns?  At that point,\nyou know none of \"x\", \"x/y\" nor \"x/y/doc\" appear in the exclude list;\notherwise you wouldn't be in \"x/y/doc\" in the first place, no?\n\nAnd the beauty of not having to check anything but directory components at\nthe tail end is that you do not have to reallocate the strings nor stuff\nthem in a list at all.\n\nYou might need to restructure the loop that walks the index which is a\nflat list needs to be restructured to match hierarchical tree walking code\nthat lets you skip the entries with the same prefix in one go, though.\n"}]}