{"thread":{"id":"10507","subject":"[PATCH] Speedup scanning for excluded files.","startedAt":"2007-10-29T07:45:26Z","lastAt":"2007-10-30T07:46:01Z","messageCount":8,"participants":["Lars Knoll","Pierre Habouzit","Junio C Hamano","Morten Welinder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"57472","messageId":"200710290845.26727.lars@trolltech.com","threadId":"10507","inReplyTo":null,"subject":"[PATCH] Speedup scanning for excluded files.","fromName":"Lars Knoll","fromEmail":"lars@trolltech.com","sentAt":"2007-10-29T07:45:26Z","receivedAt":"2007-10-29T07:45:26Z","isPatch":true,"sender":{"key":"lars@trolltech.com","avatar":null},"body":"Try to avoid a lot of work while scanning for excluded files,\nby caching some more information when setting up the exclusion\ndata structure.\n\nSpeeds up 'git runstatus' on a repository containing the Qt sources by 30% and \nreduces the amount of instructions executed (as measured by valgrind) by a \nfactor of 2.\n---\n\nI did a short timing measurement on the git repository itself, the timings \nwithout and with the patch give:\n\n[lars@dahab git (speedup)]$ time git runstatus > /dev/null\n\nreal    0m0.098s\nuser    0m0.068s\nsys     0m0.020s\n[lars@dahab git (speedup)]$ time ./git runstatus > /dev/null\n\nreal    0m0.033s\nuser    0m0.012s\nsys     0m0.016s\n\nCheers,\nLars\n\n dir.c |   58 ++++++++++++++++++++++++++++++++++++++++++----------------\n dir.h |    7 +++++++\n 2 files changed, 49 insertions(+), 16 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4c17d36..7bb5add 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char \n*name, int namelen, int pre\n \treturn retval;\n }\n \n+static int no_wildcard(const char *string)\n+{\n+    return !strchr(string, '*') && !strchr(string, '?') \n&& !strchr(string, '[') && !strchr(string, '{');\n+}\n+\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n \tstruct exclude *x = xmalloc(sizeof (*x));\n \n+        x->to_exclude = 1;\n+        if (*string == '!') {\n+            x->to_exclude = 0;\n+            string++;\n+        }\n \tx->pattern = string;\n+        x->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\n+        x->flags = 0;\n+        if (!strchr(string, '/'))\n+            x->flags |= EXC_FLAG_NODIR;\n+        if (no_wildcard(string))\n+            x->flags |= EXC_FLAG_NOWILDCARD;\n+        if (*string == '*' && no_wildcard(string+1))\n+            x->flags |= EXC_FLAG_ENDSWITH;\n \tif (which->nr == which->alloc) {\n \t\twhich->alloc = alloc_nr(which->alloc);\n \t\twhich->excludes = xrealloc(which->excludes,\n@@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int \nstk)\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n static int excluded_1(const char *pathname,\n-\t\t      int pathlen,\n+\t\t      int pathlen, const char *basename, \n \t\t      struct exclude_list *el)\n {\n \tint i;\n@@ -218,19 +236,20 @@ static int excluded_1(const char *pathname,\n \t\tfor (i = el->nr - 1; 0 <= i; i--) {\n \t\t\tstruct exclude *x = el->excludes[i];\n \t\t\tconst char *exclude = x->pattern;\n-\t\t\tint to_exclude = 1;\n+\t\t\tint to_exclude = x->to_exclude;\n \n-\t\t\tif (*exclude == '!') {\n-\t\t\t\tto_exclude = 0;\n-\t\t\t\texclude++;\n-\t\t\t}\n-\n-\t\t\tif (!strchr(exclude, '/')) {\n+\t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n-\t\t\t\tconst char *basename = strrchr(pathname, '/');\n-\t\t\t\tbasename = (basename) ? basename+1 : pathname;\n-\t\t\t\tif (fnmatch(exclude, basename, 0) == 0)\n-\t\t\t\t\treturn to_exclude;\n+                            if (x->flags & EXC_FLAG_NOWILDCARD) {\n+                                if (!strcmp(exclude, basename))\n+                                    return to_exclude;\n+                            } else if (x->flags & EXC_FLAG_ENDSWITH) {\n+                                if (!strcmp(exclude + 1, pathname + \npathlen -x->patternlen + 1))\n+                                    return to_exclude;\n+                            } else {\n+                                if (fnmatch(exclude, basename, 0) == 0)\n+                                    return to_exclude;\n+                            }\n \t\t\t}\n \t\t\telse {\n \t\t\t\t/* match with FNM_PATHNAME:\n@@ -246,10 +265,15 @@ static int excluded_1(const char *pathname,\n \t\t\t\t    strncmp(pathname, x->base, baselen))\n \t\t\t\t    continue;\n \n-\t\t\t\tif (fnmatch(exclude, pathname+baselen,\n-\t\t\t\t\t    FNM_PATHNAME) == 0)\n+                                if (x->flags & EXC_FLAG_NOWILDCARD) {\n+                                    if (!strcmp(exclude, pathname + baselen))\n+                                        return to_exclude;\n+                                } else {\n+                                    if (fnmatch(exclude, pathname+baselen,\n+                                                FNM_PATHNAME) == 0)\n \t\t\t\t\treturn to_exclude;\n-\t\t\t}\n+                                }\n+                        }\n \t\t}\n \t}\n \treturn -1; /* undecided */\n@@ -259,9 +283,11 @@ int excluded(struct dir_struct *dir, const char \n*pathname)\n {\n \tint pathlen = strlen(pathname);\n \tint st;\n+        const char *basename = strrchr(pathname, '/');\n+        basename = (basename) ? basename+1 : pathname;\n \n \tfor (st = EXC_CMDL; st <= EXC_FILE; st++) {\n-\t\tswitch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {\n+\t\tswitch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {\n \t\tcase 0:\n \t\t\treturn 0;\n \t\tcase 1:\ndiff --git a/dir.h b/dir.h\nindex a248a23..4dc3c6e 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -17,13 +17,20 @@ struct dir_entry {\n \tchar name[FLEX_ARRAY]; /* more */\n };\n \n+#define EXC_FLAG_NODIR 1\n+#define EXC_FLAG_NOWILDCARD 2\n+#define EXC_FLAG_ENDSWITH 4\n+\n struct exclude_list {\n \tint nr;\n \tint alloc;\n \tstruct exclude {\n \t\tconst char *pattern;\n+                int patternlen;            \n \t\tconst char *base;\n \t\tint baselen;\n+                int to_exclude;\n+                int flags;\n \t} **excludes;\n };\n \n-- \n1.5.3.4.383.gd90a7\n"},{"id":"57473","messageId":"20071029080234.GA22826@artemis.corp","threadId":"10507","inReplyTo":"200710290845.26727.lars@trolltech.com","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Pierre Habouzit","fromEmail":"madcoder@debian.org","sentAt":"2007-10-29T08:02:34Z","receivedAt":"2007-10-29T08:02:34Z","isPatch":true,"sender":{"key":"madcoder@debian.org","avatar":"https://avatars.githubusercontent.com/u/44708?v=4"},"body":"On Mon, Oct 29, 2007 at 07:45:26AM +0000, Lars Knoll wrote:\n> +static int no_wildcard(const char *string)\n> +{\n> +    return !strchr(string, '*') && !strchr(string, '?') \n> && !strchr(string, '[') && !strchr(string, '{');\n\n       return string[strcspn(string, \"*?[{\")] == '\\0';\n\nis faster as it doesn't scan the string 4 time in the case where there\nis no wildcard.\n\n> +}\n> +\n>  void add_exclude(const char *string, const char *base,\n>  \t\t int baselen, struct exclude_list *which)\n>  {\n>  \tstruct exclude *x = xmalloc(sizeof (*x));\n>  \n> +        x->to_exclude = 1;\n\n  You used spaces in here, and in many places of your patch.\n\n\n-- \n·O·  Pierre Habouzit\n··O                                                madcoder@debian.org\nOOO                                                http://www.madism.org\n"},{"id":"57476","messageId":"200710290959.32538.lars@trolltech.com","threadId":"10507","inReplyTo":"20071029080234.GA22826@artemis.corp","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Lars Knoll","fromEmail":"lars@trolltech.com","sentAt":"2007-10-29T08:59:32Z","receivedAt":"2007-10-29T08:59:32Z","isPatch":true,"sender":{"key":"lars@trolltech.com","avatar":null},"body":">From 51b364c9c87bec89556a2089cc5363c588ea2ff5 Mon Sep 17 00:00:00 2001\nFrom: Lars Knoll <lars@trolltech.com>\nDate: Sun, 28 Oct 2007 21:27:13 +0100\nSubject: [PATCH] Speedup scanning for excluded files.\n\nTry to avoid a lot of work scanning for excluded files,\nby caching some more information when setting up the exclusion\ndata structure.\n\nSpeeds up 'git runstatus' on a repository containing the Qt sources by 30% and\nreduces the amount of instructions executed (as measured by valgrind) by a\nfactor of 2.\n---\n\nFixed both the indentation and the no_wildcard method as Pierre pointed out. \n\nLars\n\n dir.c |   60 +++++++++++++++++++++++++++++++++++++++++++-----------------\n dir.h |    7 +++++++\n 2 files changed, 50 insertions(+), 17 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4c17d36..f4e130c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre\n \treturn retval;\n }\n \n+static int no_wildcard(const char *string)\n+{\n+\treturn string[strcspn(string, \"*?[{\")] == '\\0';\n+}\n+\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n \tstruct exclude *x = xmalloc(sizeof (*x));\n \n+\tx->to_exclude = 1;\n+\tif (*string == '!') {\n+\t\tx->to_exclude = 0;\n+\t\tstring++;\n+\t}\n \tx->pattern = string;\n+\tx->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\n+\tx->flags = 0;\n+\tif (!strchr(string, '/'))\n+\t\tx->flags |= EXC_FLAG_NODIR;\n+\tif (no_wildcard(string))\n+\t\tx->flags |= EXC_FLAG_NOWILDCARD;\n+\tif (*string == '*' && no_wildcard(string+1))\n+\t\tx->flags |= EXC_FLAG_ENDSWITH;\n \tif (which->nr == which->alloc) {\n \t\twhich->alloc = alloc_nr(which->alloc);\n \t\twhich->excludes = xrealloc(which->excludes,\n@@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int stk)\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n static int excluded_1(const char *pathname,\n-\t\t      int pathlen,\n+\t\t      int pathlen, const char *basename, \n \t\t      struct exclude_list *el)\n {\n \tint i;\n@@ -218,19 +236,20 @@ static int excluded_1(const char *pathname,\n \t\tfor (i = el->nr - 1; 0 <= i; i--) {\n \t\t\tstruct exclude *x = el->excludes[i];\n \t\t\tconst char *exclude = x->pattern;\n-\t\t\tint to_exclude = 1;\n+\t\t\tint to_exclude = x->to_exclude;\n \n-\t\t\tif (*exclude == '!') {\n-\t\t\t\tto_exclude = 0;\n-\t\t\t\texclude++;\n-\t\t\t}\n-\n-\t\t\tif (!strchr(exclude, '/')) {\n+\t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n-\t\t\t\tconst char *basename = strrchr(pathname, '/');\n-\t\t\t\tbasename = (basename) ? basename+1 : pathname;\n-\t\t\t\tif (fnmatch(exclude, basename, 0) == 0)\n-\t\t\t\t\treturn to_exclude;\n+\t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n+\t\t\t\t\tif (!strcmp(exclude, basename))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else if (x->flags & EXC_FLAG_ENDSWITH) {\n+\t\t\t\t\tif (!strcmp(exclude + 1, pathname + pathlen -x->patternlen + 1))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else {\n+\t\t\t\t\tif (fnmatch(exclude, basename, 0) == 0)\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t}\n \t\t\t}\n \t\t\telse {\n \t\t\t\t/* match with FNM_PATHNAME:\n@@ -246,9 +265,14 @@ static int excluded_1(const char *pathname,\n \t\t\t\t    strncmp(pathname, x->base, baselen))\n \t\t\t\t    continue;\n \n-\t\t\t\tif (fnmatch(exclude, pathname+baselen,\n-\t\t\t\t\t    FNM_PATHNAME) == 0)\n-\t\t\t\t\treturn to_exclude;\n+\t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n+\t\t\t\t\tif (!strcmp(exclude, pathname + baselen))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else {\n+\t\t\t\t\tif (fnmatch(exclude, pathname+baselen,\n+\t\t\t\t\t\t    FNM_PATHNAME) == 0)\n+\t\t\t\t\t    return to_exclude;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\n@@ -259,9 +283,11 @@ int excluded(struct dir_struct *dir, const char *pathname)\n {\n \tint pathlen = strlen(pathname);\n \tint st;\n-\n+\tconst char *basename = strrchr(pathname, '/');\n+\tbasename = (basename) ? basename+1 : pathname;\n+\t\n \tfor (st = EXC_CMDL; st <= EXC_FILE; st++) {\n-\t\tswitch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {\n+\t\tswitch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {\n \t\tcase 0:\n \t\t\treturn 0;\n \t\tcase 1:\ndiff --git a/dir.h b/dir.h\nindex a248a23..3ce8dbe 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -17,13 +17,20 @@ struct dir_entry {\n \tchar name[FLEX_ARRAY]; /* more */\n };\n \n+#define EXC_FLAG_NODIR 1\n+#define EXC_FLAG_NOWILDCARD 2\n+#define EXC_FLAG_ENDSWITH 4\n+\n struct exclude_list {\n \tint nr;\n \tint alloc;\n \tstruct exclude {\n \t\tconst char *pattern;\n+\t\tint patternlen;\t\t   \n \t\tconst char *base;\n \t\tint baselen;\n+\t\tint to_exclude;\n+\t\tint flags;\n \t} **excludes;\n };\n \n-- \n1.5.3.4.383.gd90a7\n"},{"id":"57492","messageId":"7v8x5ltvjj.fsf@gitster.siamese.dyndns.org","threadId":"10507","inReplyTo":"200710290959.32538.lars@trolltech.com","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-10-29T22:17:52Z","receivedAt":"2007-10-29T22:17:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks.  This looks obviously correct and tempts me to apply\ndirectly to 'master'.\n\nThere are similar logic on the gitattributes side (attr.c) which\nmight benefit from such an optimization as well.  In the longer\nrun, we might even want to introduce 'ignored' attribute to the\ngitattributes mechanism and make the unified result to supersede\ngitignore.\n"},{"id":"57494","messageId":"118833cc0710291559kbd874a8o8111b9495090ef27@mail.gmail.com","threadId":"10507","inReplyTo":"200710290959.32538.lars@trolltech.com","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Morten Welinder","fromEmail":"mwelinder@gmail.com","sentAt":"2007-10-29T22:59:00Z","receivedAt":"2007-10-29T22:59:00Z","isPatch":true,"sender":{"key":"mwelinder@gmail.com","avatar":null},"body":"> +                               } else if (x->flags & EXC_FLAG_ENDSWITH) {\n> +                                       if (!strcmp(exclude + 1, pathname + pathlen -x->patternlen + 1))\n\nIs there some guarantee that the result of that subtraction is still within\nthe string?\n\nMorten\n"},{"id":"57498","messageId":"7vzly1sc7p.fsf@gitster.siamese.dyndns.org","threadId":"10507","inReplyTo":"118833cc0710291559kbd874a8o8111b9495090ef27@mail.gmail.com","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-10-30T00:00:42Z","receivedAt":"2007-10-30T00:00:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Morten Welinder\" <mwelinder@gmail.com> writes:\n\n>> +                               } else if (x->flags & EXC_FLAG_ENDSWITH) {\n>> +                                       if (!strcmp(exclude + 1, pathname + pathlen -x->patternlen + 1))\n>\n> Is there some guarantee that the result of that subtraction is still within\n> the string?\n\nGood eyes.\n\nIf pattern is \"*.exe\", patternlen is 5, and strcmp wants to\ncompare 4 chars, so pathlen is better be at least that long, and\nwe do allow that pattern to match a hidden file \".exe\".\n\nLike this?\n\n\tif (x->patternlen - 1 <= pathlen &&\n        \t!strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))\n\t\treturn to_exclude;\n"},{"id":"57535","messageId":"200710300828.49840.lars@trolltech.com","threadId":"10507","inReplyTo":"7vzly1sc7p.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Speedup scanning for excluded files.","fromName":"Lars Knoll","fromEmail":"lars@trolltech.com","sentAt":"2007-10-30T07:28:49Z","receivedAt":"2007-10-30T07:28:49Z","isPatch":true,"sender":{"key":"lars@trolltech.com","avatar":null},"body":"On Tuesday 30 October 2007, Junio C Hamano wrote:\n> \"Morten Welinder\" <mwelinder@gmail.com> writes:\n> >> +                               } else if (x->flags & EXC_FLAG_ENDSWITH)\n> >> { +                                       if (!strcmp(exclude + 1,\n> >> pathname + pathlen -x->patternlen + 1))\n> >\n> > Is there some guarantee that the result of that subtraction is still\n> > within the string?\n>\n> Good eyes.\n>\n> If pattern is \"*.exe\", patternlen is 5, and strcmp wants to\n> compare 4 chars, so pathlen is better be at least that long, and\n> we do allow that pattern to match a hidden file \".exe\".\n>\n> Like this?\n>\n> \tif (x->patternlen - 1 <= pathlen &&\n>         \t!strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))\n> \t\treturn to_exclude;\n\nYes, that looks right. Thanks for catching that one.\n\nLars\n"},{"id":"57536","messageId":"200710300846.02010.lars@trolltech.com","threadId":"10507","inReplyTo":"200710300828.49840.lars@trolltech.com","subject":"[PATCH v3] Speedup scanning for excluded files.","fromName":"Lars Knoll","fromEmail":"lars@trolltech.com","sentAt":"2007-10-30T07:46:01Z","receivedAt":"2007-10-30T07:46:01Z","isPatch":true,"sender":{"key":"lars@trolltech.com","avatar":null},"body":"Try to avoid a lot of work scanning for excluded files,\nby caching some more information when setting up the exclusion\ndata structure.\n\nSpeeds up 'git runstatus' on a repository containing the Qt sources by 30% and\nreduces the amount of instructions executed (as measured by valgrind) by a\nfactor of 2. A 'git runstatus' on the git repository goes from 100M instructions\ndown to about 22M.\n\nSigned-off-by: Lars Knoll <lars@trolltech.com>\n---\n\nIncluded an out of bounds check for the issue Morten found.\n\n dir.c |   61 ++++++++++++++++++++++++++++++++++++++++++++-----------------\n dir.h |    7 +++++++\n 2 files changed, 51 insertions(+), 17 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 4c17d36..b3d7462 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre\n \treturn retval;\n }\n \n+static int no_wildcard(const char *string)\n+{\n+\treturn string[strcspn(string, \"*?[{\")] == '\\0';\n+}\n+\n void add_exclude(const char *string, const char *base,\n \t\t int baselen, struct exclude_list *which)\n {\n \tstruct exclude *x = xmalloc(sizeof (*x));\n \n+\tx->to_exclude = 1;\n+\tif (*string == '!') {\n+\t\tx->to_exclude = 0;\n+\t\tstring++;\n+\t}\n \tx->pattern = string;\n+\tx->patternlen = strlen(string);\n \tx->base = base;\n \tx->baselen = baselen;\n+\tx->flags = 0;\n+\tif (!strchr(string, '/'))\n+\t\tx->flags |= EXC_FLAG_NODIR;\n+\tif (no_wildcard(string))\n+\t\tx->flags |= EXC_FLAG_NOWILDCARD;\n+\tif (*string == '*' && no_wildcard(string+1))\n+\t\tx->flags |= EXC_FLAG_ENDSWITH;\n \tif (which->nr == which->alloc) {\n \t\twhich->alloc = alloc_nr(which->alloc);\n \t\twhich->excludes = xrealloc(which->excludes,\n@@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int stk)\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n static int excluded_1(const char *pathname,\n-\t\t      int pathlen,\n+\t\t      int pathlen, const char *basename, \n \t\t      struct exclude_list *el)\n {\n \tint i;\n@@ -218,19 +236,21 @@ static int excluded_1(const char *pathname,\n \t\tfor (i = el->nr - 1; 0 <= i; i--) {\n \t\t\tstruct exclude *x = el->excludes[i];\n \t\t\tconst char *exclude = x->pattern;\n-\t\t\tint to_exclude = 1;\n+\t\t\tint to_exclude = x->to_exclude;\n \n-\t\t\tif (*exclude == '!') {\n-\t\t\t\tto_exclude = 0;\n-\t\t\t\texclude++;\n-\t\t\t}\n-\n-\t\t\tif (!strchr(exclude, '/')) {\n+\t\t\tif (x->flags & EXC_FLAG_NODIR) {\n \t\t\t\t/* match basename */\n-\t\t\t\tconst char *basename = strrchr(pathname, '/');\n-\t\t\t\tbasename = (basename) ? basename+1 : pathname;\n-\t\t\t\tif (fnmatch(exclude, basename, 0) == 0)\n-\t\t\t\t\treturn to_exclude;\n+\t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n+\t\t\t\t\tif (!strcmp(exclude, basename))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else if (x->flags & EXC_FLAG_ENDSWITH) {\n+\t\t\t\t\tif (x->patternlen - 1 <= pathlen &&\n+                                            !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else {\n+\t\t\t\t\tif (fnmatch(exclude, basename, 0) == 0)\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t}\n \t\t\t}\n \t\t\telse {\n \t\t\t\t/* match with FNM_PATHNAME:\n@@ -246,9 +266,14 @@ static int excluded_1(const char *pathname,\n \t\t\t\t    strncmp(pathname, x->base, baselen))\n \t\t\t\t    continue;\n \n-\t\t\t\tif (fnmatch(exclude, pathname+baselen,\n-\t\t\t\t\t    FNM_PATHNAME) == 0)\n-\t\t\t\t\treturn to_exclude;\n+\t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n+\t\t\t\t\tif (!strcmp(exclude, pathname + baselen))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t} else {\n+\t\t\t\t\tif (fnmatch(exclude, pathname+baselen,\n+\t\t\t\t\t\t    FNM_PATHNAME) == 0)\n+\t\t\t\t\t    return to_exclude;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\n@@ -259,9 +284,11 @@ int excluded(struct dir_struct *dir, const char *pathname)\n {\n \tint pathlen = strlen(pathname);\n \tint st;\n-\n+\tconst char *basename = strrchr(pathname, '/');\n+\tbasename = (basename) ? basename+1 : pathname;\n+\t\n \tfor (st = EXC_CMDL; st <= EXC_FILE; st++) {\n-\t\tswitch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {\n+\t\tswitch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {\n \t\tcase 0:\n \t\t\treturn 0;\n \t\tcase 1:\ndiff --git a/dir.h b/dir.h\nindex a248a23..3ce8dbe 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -17,13 +17,20 @@ struct dir_entry {\n \tchar name[FLEX_ARRAY]; /* more */\n };\n \n+#define EXC_FLAG_NODIR 1\n+#define EXC_FLAG_NOWILDCARD 2\n+#define EXC_FLAG_ENDSWITH 4\n+\n struct exclude_list {\n \tint nr;\n \tint alloc;\n \tstruct exclude {\n \t\tconst char *pattern;\n+\t\tint patternlen;\t\t   \n \t\tconst char *base;\n \t\tint baselen;\n+\t\tint to_exclude;\n+\t\tint flags;\n \t} **excludes;\n };\n \n-- \n1.5.3.4.383.gd90a7\n"}]}