{"thread":{"id":"31813","subject":"[PATCH 0/4] nd/attr-match-optim-more updates","startedAt":"2012-10-14T11:55:38Z","lastAt":"2012-10-14T18:09:16Z","messageCount":6,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"201165","messageId":"1350215742-20761-1-git-send-email-pclouds@gmail.com","threadId":"31813","inReplyTo":null,"subject":"[PATCH 0/4] nd/attr-match-optim-more updates","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-14T11:55:38Z","receivedAt":"2012-10-14T11:55:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is on top of nd/attr-match-optim-more to fix the bug I sent\nrecently [1] sharing the code, thus sharing any fixes.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/207652\n\nNguyễn Thái Ngọc Duy (4):\n  exclude: stricten a length check in EXC_FLAG_ENDSWITH case\n  exclude: fix a bug in prefix compare optimization\n  exclude/attr: share basename matching code\n  exclude/attr: share full pathname matching code\n\n attr.c                             |  50 +++------------\n dir.c                              | 121 +++++++++++++++++++++++--------------\n dir.h                              |   5 ++\n t/t3001-ls-files-others-exclude.sh |   6 ++\n 4 files changed, 95 insertions(+), 87 deletions(-)\n\n-- \n1.8.0.rc2.11.g2b79d01\n"},{"id":"201167","messageId":"1350215742-20761-2-git-send-email-pclouds@gmail.com","threadId":"31813","inReplyTo":"1350215742-20761-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/4] exclude: stricten a length check in EXC_FLAG_ENDSWITH case","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-14T11:55:39Z","receivedAt":"2012-10-14T11:55:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This block of code deals with the \"basename\" part only, which has the\nlength of \"pathlen - (basename - pathname)\". Stricten the length check\nand remove \"pathname\" from the main expression to avoid confusion.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex cddf043..d81498e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -561,8 +561,9 @@ int excluded_from_list(const char *pathname,\n \t\t\t\tif (!strcmp_icase(exclude, basename))\n \t\t\t\t\treturn to_exclude;\n \t\t\t} else if (x->flags & EXC_FLAG_ENDSWITH) {\n-\t\t\t\tif (x->patternlen - 1 <= pathlen &&\n-\t\t\t\t    !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))\n+\t\t\t\tint len = pathlen - (basename - pathname);\n+\t\t\t\tif (x->patternlen - 1 <= len &&\n+\t\t\t\t    !strcmp_icase(exclude + 1, basename + len - x->patternlen + 1))\n \t\t\t\t\treturn to_exclude;\n \t\t\t} else {\n \t\t\t\tif (fnmatch_icase(exclude, basename, 0) == 0)\n-- \n1.8.0.rc2.11.g2b79d01\n"},{"id":"201166","messageId":"1350215742-20761-3-git-send-email-pclouds@gmail.com","threadId":"31813","inReplyTo":"1350215742-20761-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/4] exclude: fix a bug in prefix compare optimization","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-14T11:55:40Z","receivedAt":"2012-10-14T11:55:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"When \"namelen\" becomes zero at this stage, we have matched the fixed\npart, but whether it actually matches the pattern still depends on the\npattern in \"exclude\". As demonstrated in t3001, path \"three/a.3\"\nexists and it matches the \"three/a.3\" part in pattern \"three/a.3[abc]\",\nbut that does not mean a true match.\n\nDon't be too optimistic and let fnmatch() do the job.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c                              | 2 +-\n t/t3001-ls-files-others-exclude.sh | 6 ++++++\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/dir.c b/dir.c\nindex d81498e..0f4aea6 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -601,7 +601,7 @@ int excluded_from_list(const char *pathname,\n \t\t\tnamelen -= prefix;\n \t\t}\n \n-\t\tif (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))\n+\t\tif (!fnmatch_icase(exclude, name, FNM_PATHNAME))\n \t\t\treturn to_exclude;\n \t}\n \treturn -1; /* undecided */\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex c8fe978..dc2f045 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -214,4 +214,10 @@ test_expect_success 'subdirectory ignore (l1)' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'pattern matches prefix completely' '\n+\t: >expect &&\n+\tgit ls-files -i -o --exclude \"/three/a.3[abc]\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.8.0.rc2.11.g2b79d01\n"},{"id":"201168","messageId":"1350215742-20761-4-git-send-email-pclouds@gmail.com","threadId":"31813","inReplyTo":"1350215742-20761-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/4] exclude/attr: share basename matching code","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-14T11:55:41Z","receivedAt":"2012-10-14T11:55:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"match_basename's declaration in dir.h does not have any description to\ndiscourage the use of this function elsewhere as this function is\nhighly tied to how excluded_from_list and path_matches work.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n attr.c | 15 ++++-----------\n dir.c  | 37 ++++++++++++++++++++++++-------------\n dir.h  |  2 ++\n 3 files changed, 30 insertions(+), 24 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 0964033..a28ce0d 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -663,17 +663,10 @@ static int path_matches(const char *pathname, int pathlen,\n \tint namelen;\n \n \tif (pat->flags & EXC_FLAG_NODIR) {\n-\t\tif (prefix == pat->patternlen &&\n-\t\t    !strcmp_icase(pattern, basename))\n-\t\t\treturn 1;\n-\n-\t\tif (pat->flags & EXC_FLAG_ENDSWITH &&\n-\t\t    pat->patternlen - 1 <= pathlen &&\n-\t\t    !strcmp_icase(pattern + 1, pathname +\n-\t\t\t\t  pathlen - pat->patternlen + 1))\n-\t\t\treturn 1;\n-\n-\t\treturn (fnmatch_icase(pattern, basename, 0) == 0);\n+\t\treturn match_basename(basename,\n+\t\t\t\t      pathlen - (basename - pathname),\n+\t\t\t\t      pattern, prefix,\n+\t\t\t\t      pat->patternlen, pat->flags);\n \t}\n \t/*\n \t * match with FNM_PATHNAME; the pattern has base implicitly\ndiff --git a/dir.c b/dir.c\nindex 0f4aea6..42c42cd 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -530,6 +530,25 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \tdir->basebuf[baselen] = '\\0';\n }\n \n+int match_basename(const char *basename, int basenamelen,\n+\t\t   const char *pattern, int prefix, int patternlen,\n+\t\t   int flags)\n+{\n+\tif (prefix == patternlen) {\n+\t\tif (!strcmp_icase(pattern, basename))\n+\t\t\treturn 1;\n+\t} else if (flags & EXC_FLAG_ENDSWITH) {\n+\t\tif (patternlen - 1 <= basenamelen &&\n+\t\t    !strcmp_icase(pattern + 1,\n+\t\t\t\t  basename + basenamelen - patternlen + 1))\n+\t\t\treturn 1;\n+\t} else {\n+\t\tif (fnmatch_icase(pattern, basename, 0) == 0)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n /* Scan the list and let the last match determine the fate.\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n@@ -556,19 +575,11 @@ int excluded_from_list(const char *pathname,\n \t\t}\n \n \t\tif (x->flags & EXC_FLAG_NODIR) {\n-\t\t\t/* match basename */\n-\t\t\tif (prefix == x->patternlen) {\n-\t\t\t\tif (!strcmp_icase(exclude, basename))\n-\t\t\t\t\treturn to_exclude;\n-\t\t\t} else if (x->flags & EXC_FLAG_ENDSWITH) {\n-\t\t\t\tint len = pathlen - (basename - pathname);\n-\t\t\t\tif (x->patternlen - 1 <= len &&\n-\t\t\t\t    !strcmp_icase(exclude + 1, basename + len - x->patternlen + 1))\n-\t\t\t\t\treturn to_exclude;\n-\t\t\t} else {\n-\t\t\t\tif (fnmatch_icase(exclude, basename, 0) == 0)\n-\t\t\t\t\treturn to_exclude;\n-\t\t\t}\n+\t\t\tif (match_basename(basename,\n+\t\t\t\t\t   pathlen - (basename - pathname),\n+\t\t\t\t\t   exclude, prefix, x->patternlen,\n+\t\t\t\t\t   x->flags))\n+\t\t\t\treturn to_exclude;\n \t\t\tcontinue;\n \t\t}\n \ndiff --git a/dir.h b/dir.h\nindex fd5c2aa..d416c5a 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -78,6 +78,8 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const\n \n extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,\n \t\t\t      int *dtype, struct exclude_list *el);\n+extern int match_basename(const char *, int,\n+\t\t\t  const char *, int, int, int);\n struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);\n \n /*\n-- \n1.8.0.rc2.11.g2b79d01\n"},{"id":"201169","messageId":"1350215742-20761-5-git-send-email-pclouds@gmail.com","threadId":"31813","inReplyTo":"1350215742-20761-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/4] exclude/attr: share full pathname matching code","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-14T11:55:42Z","receivedAt":"2012-10-14T11:55:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"match_pathname's declaration in dir.h does not have any description to\ndiscourage the use of this function elsewhere as this function is\nhighly tied to how excluded_from_list and path_matches work.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n attr.c | 35 +++------------------------\n dir.c  | 85 +++++++++++++++++++++++++++++++++++++++++-------------------------\n dir.h  |  3 +++\n 3 files changed, 59 insertions(+), 64 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a28ce0d..2fc6353 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -659,8 +659,6 @@ static int path_matches(const char *pathname, int pathlen,\n {\n \tconst char *pattern = pat->pattern;\n \tint prefix = pat->nowildcardlen;\n-\tconst char *name;\n-\tint namelen;\n \n \tif (pat->flags & EXC_FLAG_NODIR) {\n \t\treturn match_basename(basename,\n@@ -668,36 +666,9 @@ static int path_matches(const char *pathname, int pathlen,\n \t\t\t\t      pattern, prefix,\n \t\t\t\t      pat->patternlen, pat->flags);\n \t}\n-\t/*\n-\t * match with FNM_PATHNAME; the pattern has base implicitly\n-\t * in front of it.\n-\t */\n-\tif (*pattern == '/') {\n-\t\tpattern++;\n-\t\tprefix--;\n-\t}\n-\n-\t/*\n-\t * note: unlike excluded_from_list, baselen here does not\n-\t * count the trailing slash, and base[] does not end with\n-\t * a trailing slash, either.\n-\t */\n-\tif (pathlen < baselen + 1 ||\n-\t    (baselen && pathname[baselen] != '/') ||\n-\t    strncmp_icase(pathname, base, baselen))\n-\t\treturn 0;\n-\n-\tnamelen = baselen ? pathlen - baselen - 1 : pathlen;\n-\tname = pathname + pathlen - namelen;\n-\n-\t/*\n-\t * if the non-wildcard part is longer than the remaining\n-\t * pathname, surely it cannot match.\n-\t */\n-\tif (!namelen || prefix > namelen)\n-\t\treturn 0;\n-\n-\treturn fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;\n+\treturn match_pathname(pathname, pathlen,\n+\t\t\t      base, baselen,\n+\t\t\t      pattern, prefix, pat->patternlen, pat->flags);\n }\n \n static int macroexpand_one(int attr_nr, int rem);\ndiff --git a/dir.c b/dir.c\nindex 42c42cd..ee8e711 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -549,6 +549,53 @@ int match_basename(const char *basename, int basenamelen,\n \treturn 0;\n }\n \n+int match_pathname(const char *pathname, int pathlen,\n+\t\t   const char *base, int baselen,\n+\t\t   const char *pattern, int prefix, int patternlen,\n+\t\t   int flags)\n+{\n+\tconst char *name;\n+\tint namelen;\n+\n+\t/*\n+\t * match with FNM_PATHNAME; the pattern has base implicitly\n+\t * in front of it.\n+\t */\n+\tif (*pattern == '/') {\n+\t\tpattern++;\n+\t\tprefix--;\n+\t}\n+\n+\t/*\n+\t * baselen does not count the trailing slash. base[] may or\n+\t * may not end with a trailing slash though.\n+\t */\n+\tif (pathlen < baselen + 1 ||\n+\t    (baselen && pathname[baselen] != '/') ||\n+\t    strncmp_icase(pathname, base, baselen))\n+\t\treturn 0;\n+\n+\tnamelen = baselen ? pathlen - baselen - 1 : pathlen;\n+\tname = pathname + pathlen - namelen;\n+\n+\tif (prefix) {\n+\t\t/*\n+\t\t * if the non-wildcard part is longer than the\n+\t\t * remaining pathname, surely it cannot match.\n+\t\t */\n+\t\tif (prefix > namelen)\n+\t\t\treturn 0;\n+\n+\t\tif (strncmp_icase(pattern, name, prefix))\n+\t\t\treturn 0;\n+\t\tpattern += prefix;\n+\t\tname    += prefix;\n+\t\tnamelen -= prefix;\n+\t}\n+\n+\treturn fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;\n+}\n+\n /* Scan the list and let the last match determine the fate.\n  * Return 1 for exclude, 0 for include and -1 for undecided.\n  */\n@@ -563,9 +610,9 @@ int excluded_from_list(const char *pathname,\n \n \tfor (i = el->nr - 1; 0 <= i; i--) {\n \t\tstruct exclude *x = el->excludes[i];\n-\t\tconst char *name, *exclude = x->pattern;\n+\t\tconst char *exclude = x->pattern;\n \t\tint to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;\n-\t\tint namelen, prefix = x->nowildcardlen;\n+\t\tint prefix = x->nowildcardlen;\n \n \t\tif (x->flags & EXC_FLAG_MUSTBEDIR) {\n \t\t\tif (*dtype == DT_UNKNOWN)\n@@ -583,36 +630,10 @@ int excluded_from_list(const char *pathname,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* match with FNM_PATHNAME:\n-\t\t * exclude has base (baselen long) implicitly in front of it.\n-\t\t */\n-\t\tif (*exclude == '/') {\n-\t\t\texclude++;\n-\t\t\tprefix--;\n-\t\t}\n-\n-\t\tif (pathlen < x->baselen ||\n-\t\t    (x->baselen && pathname[x->baselen-1] != '/') ||\n-\t\t    strncmp_icase(pathname, x->base, x->baselen))\n-\t\t\tcontinue;\n-\n-\t\tnamelen = x->baselen ? pathlen - x->baselen : pathlen;\n-\t\tname = pathname + pathlen  - namelen;\n-\n-\t\t/* if the non-wildcard part is longer than the\n-\t\t   remaining pathname, surely it cannot match */\n-\t\tif (prefix > namelen)\n-\t\t\tcontinue;\n-\n-\t\tif (prefix) {\n-\t\t\tif (strncmp_icase(exclude, name, prefix))\n-\t\t\t\tcontinue;\n-\t\t\texclude += prefix;\n-\t\t\tname    += prefix;\n-\t\t\tnamelen -= prefix;\n-\t\t}\n-\n-\t\tif (!fnmatch_icase(exclude, name, FNM_PATHNAME))\n+\t\tassert(x->baselen == 0 || x->base[x->baselen - 1] == '/');\n+\t\tif (match_pathname(pathname, pathlen,\n+\t\t\t\t   x->base, x->baselen ? x->baselen - 1 : 0,\n+\t\t\t\t   exclude, prefix, x->patternlen, x->flags))\n \t\t\treturn to_exclude;\n \t}\n \treturn -1; /* undecided */\ndiff --git a/dir.h b/dir.h\nindex d416c5a..11d054a 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -80,6 +80,9 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas\n \t\t\t      int *dtype, struct exclude_list *el);\n extern int match_basename(const char *, int,\n \t\t\t  const char *, int, int, int);\n+extern int match_pathname(const char *, int,\n+\t\t\t  const char *, int,\n+\t\t\t  const char *, int, int, int);\n struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);\n \n /*\n-- \n1.8.0.rc2.11.g2b79d01\n"},{"id":"201189","messageId":"7vzk3px6wj.fsf@alter.siamese.dyndns.org","threadId":"31813","inReplyTo":"1350215742-20761-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 3/4] exclude/attr: share basename matching code","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-10-14T18:09:16Z","receivedAt":"2012-10-14T18:09:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> match_basename's declaration in dir.h does not have any description to\n> discourage the use of this function elsewhere as this function is\n> highly tied to how excluded_from_list and path_matches work.\n\nIf you do want to discourage, please explicitly describe it as such.\n\nI actually think it should have an API description.  The meaning of\nits parameters and how you would formulate them is fairly clear and\nthis is a good example of a simple-and-stupid function that is\ndesigned to do one thing and do it well.\n\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  attr.c | 15 ++++-----------\n>  dir.c  | 37 ++++++++++++++++++++++++-------------\n>  dir.h  |  2 ++\n>  3 files changed, 30 insertions(+), 24 deletions(-)\n>\n> diff --git a/attr.c b/attr.c\n> index 0964033..a28ce0d 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -663,17 +663,10 @@ static int path_matches(const char *pathname, int pathlen,\n>  \tint namelen;\n>  \n>  \tif (pat->flags & EXC_FLAG_NODIR) {\n> -\t\tif (prefix == pat->patternlen &&\n> -\t\t    !strcmp_icase(pattern, basename))\n> -\t\t\treturn 1;\n> -\n> -\t\tif (pat->flags & EXC_FLAG_ENDSWITH &&\n> -\t\t    pat->patternlen - 1 <= pathlen &&\n> -\t\t    !strcmp_icase(pattern + 1, pathname +\n> -\t\t\t\t  pathlen - pat->patternlen + 1))\n> -\t\t\treturn 1;\n> -\n> -\t\treturn (fnmatch_icase(pattern, basename, 0) == 0);\n> +\t\treturn match_basename(basename,\n> +\t\t\t\t      pathlen - (basename - pathname),\n> +\t\t\t\t      pattern, prefix,\n> +\t\t\t\t      pat->patternlen, pat->flags);\n>  \t}\n>  \t/*\n>  \t * match with FNM_PATHNAME; the pattern has base implicitly\n> diff --git a/dir.c b/dir.c\n> index 0f4aea6..42c42cd 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -530,6 +530,25 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n>  \tdir->basebuf[baselen] = '\\0';\n>  }\n>  \n> +int match_basename(const char *basename, int basenamelen,\n> +\t\t   const char *pattern, int prefix, int patternlen,\n> +\t\t   int flags)\n> +{\n> +\tif (prefix == patternlen) {\n> +\t\tif (!strcmp_icase(pattern, basename))\n> +\t\t\treturn 1;\n> +\t} else if (flags & EXC_FLAG_ENDSWITH) {\n> +\t\tif (patternlen - 1 <= basenamelen &&\n> +\t\t    !strcmp_icase(pattern + 1,\n> +\t\t\t\t  basename + basenamelen - patternlen + 1))\n> +\t\t\treturn 1;\n> +\t} else {\n> +\t\tif (fnmatch_icase(pattern, basename, 0) == 0)\n> +\t\t\treturn 1;\n> +\t}\n> +\treturn 0;\n> +}\n> +\n>  /* Scan the list and let the last match determine the fate.\n>   * Return 1 for exclude, 0 for include and -1 for undecided.\n>   */\n> @@ -556,19 +575,11 @@ int excluded_from_list(const char *pathname,\n>  \t\t}\n>  \n>  \t\tif (x->flags & EXC_FLAG_NODIR) {\n> -\t\t\t/* match basename */\n> -\t\t\tif (prefix == x->patternlen) {\n> -\t\t\t\tif (!strcmp_icase(exclude, basename))\n> -\t\t\t\t\treturn to_exclude;\n> -\t\t\t} else if (x->flags & EXC_FLAG_ENDSWITH) {\n> -\t\t\t\tint len = pathlen - (basename - pathname);\n> -\t\t\t\tif (x->patternlen - 1 <= len &&\n> -\t\t\t\t    !strcmp_icase(exclude + 1, basename + len - x->patternlen + 1))\n> -\t\t\t\t\treturn to_exclude;\n> -\t\t\t} else {\n> -\t\t\t\tif (fnmatch_icase(exclude, basename, 0) == 0)\n> -\t\t\t\t\treturn to_exclude;\n> -\t\t\t}\n> +\t\t\tif (match_basename(basename,\n> +\t\t\t\t\t   pathlen - (basename - pathname),\n> +\t\t\t\t\t   exclude, prefix, x->patternlen,\n> +\t\t\t\t\t   x->flags))\n> +\t\t\t\treturn to_exclude;\n>  \t\t\tcontinue;\n>  \t\t}\n>  \n> diff --git a/dir.h b/dir.h\n> index fd5c2aa..d416c5a 100644\n> --- a/dir.h\n> +++ b/dir.h\n> @@ -78,6 +78,8 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const\n>  \n>  extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,\n>  \t\t\t      int *dtype, struct exclude_list *el);\n> +extern int match_basename(const char *, int,\n> +\t\t\t  const char *, int, int, int);\n>  struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);\n>  \n>  /*\n"}]}