{"thread":{"id":"31733","subject":"[PATCH 05/10] Import wildmatch from rsync","startedAt":"2012-10-05T04:40:59Z","lastAt":"2012-10-08T15:50:03Z","messageCount":22,"participants":["Nguyễn Thái Ngọc Duy","Peter Krefting","Nguyen Thai Ngoc Duy","Junio C Hamano","Thiago Farina","Joachim Schmitz"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"200602","messageId":"1349412069-627-1-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":null,"subject":"[PATCH 00/10] nd/wildmatch take 2","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:40:59Z","receivedAt":"2012-10-05T04:40:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The first four patches are ignore/attr cleanups. The following imports\nwildmatch, nothing new there. The last patch limits allowed syntax to\na safe subset: \"abc/**\", \"**/def\" and \"abc/**/def\".\n\nNguyễn Thái Ngọc Duy (10):\n  gitignore: make pattern parsing code a separate function\n  attr: avoid strlen() on every match\n  attr: avoid searching for basename on every match\n  attr: more matching optimizations from .gitignore\n  Import wildmatch from rsync\n  wildmatch: remove static variable force_lower_case\n  wildmatch: fix case-insensitive matching\n  Integrate wildmatch to git\n  Support \"**\" in .gitignore and .gitattributes patterns using\n    wildmatch()\n  gitignore: forbid \"abc**def\"\n\n .gitignore                         |   1 +\n Documentation/gitattributes.txt    |   2 +\n Documentation/gitignore.txt        |   5 +\n Makefile                           |   3 +\n attr.c                             |  89 ++++++++--\n dir.c                              |  82 ++++++---\n dir.h                              |   3 +-\n t/t0003-attributes.sh              |  22 +++\n t/t3001-ls-files-others-exclude.sh |  16 ++\n t/t3070-wildmatch.sh               |  27 +++\n t/t3070/wildtest.txt               | 165 +++++++++++++++++\n test-wildmatch.c                   | 208 ++++++++++++++++++++++\n wildmatch.c                        | 355 +++++++++++++++++++++++++++++++++++++\n wildmatch.h                        |   6 +\n 14 files changed, 942 insertions(+), 42 deletions(-)\n create mode 100755 t/t3070-wildmatch.sh\n create mode 100644 t/t3070/wildtest.txt\n create mode 100644 test-wildmatch.c\n create mode 100644 wildmatch.c\n create mode 100644 wildmatch.h\n\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200606","messageId":"1349412069-627-2-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 01/10] gitignore: make pattern parsing code a separate function","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:00Z","receivedAt":"2012-10-05T04:41:00Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This function can later be reused by attr.c. Also turn to_exclude\nfield into a flag.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n If we go with glob->regex conversion way, this is where we could\n rewrite the pattern (and set EXC_FLAG_REGEX).\n\n dir.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++---------------------\n dir.h |  2 +-\n 2 files changed, 50 insertions(+), 23 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 240bf0c..cd13920 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -308,42 +308,69 @@ static int no_wildcard(const char *string)\n \treturn string[simple_length(string)] == '\\0';\n }\n \n+static void parse_exclude_pattern(const char **pattern,\n+\t\t\t\t  int *patternlen,\n+\t\t\t\t  int *flags,\n+\t\t\t\t  int *nowildcardlen)\n+{\n+\tconst char *p = *pattern;\n+\tsize_t i, len;\n+\n+\t*flags = 0;\n+\tif (*p == '!') {\n+\t\t*flags |= EXC_FLAG_NEGATIVE;\n+\t\tp++;\n+\t}\n+\tlen = strlen(p);\n+\tif (len && p[len - 1] == '/') {\n+\t\tlen--;\n+\t\t*flags |= EXC_FLAG_MUSTBEDIR;\n+\t}\n+\tfor (i = 0; i < len; i++) {\n+\t\tif (p[i] == '/')\n+\t\t\tbreak;\n+\t}\n+\tif (i == len)\n+\t\t*flags |= EXC_FLAG_NODIR;\n+\t*nowildcardlen = simple_length(p);\n+\t/*\n+\t * we should have excluded the trailing slash from 'p' too,\n+\t * but that's one more allocation. Instead just make sure\n+\t * nowildcardlen does not exceed real patternlen\n+\t */\n+\tif (*nowildcardlen > len)\n+\t\t*nowildcardlen = len;\n+\tif (*p == '*' && no_wildcard(p + 1))\n+\t\t*flags |= EXC_FLAG_ENDSWITH;\n+\t*pattern = p;\n+\t*patternlen = len;\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;\n-\tsize_t len;\n-\tint to_exclude = 1;\n-\tint flags = 0;\n+\tint patternlen;\n+\tint flags;\n+\tint nowildcardlen;\n \n-\tif (*string == '!') {\n-\t\tto_exclude = 0;\n-\t\tstring++;\n-\t}\n-\tlen = strlen(string);\n-\tif (len && string[len - 1] == '/') {\n+\tparse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);\n+\tif (flags & EXC_FLAG_MUSTBEDIR) {\n \t\tchar *s;\n-\t\tx = xmalloc(sizeof(*x) + len);\n+\t\tx = xmalloc(sizeof(*x) + patternlen + 1);\n \t\ts = (char *)(x+1);\n-\t\tmemcpy(s, string, len - 1);\n-\t\ts[len - 1] = '\\0';\n-\t\tstring = s;\n+\t\tmemcpy(s, string, patternlen);\n+\t\ts[patternlen] = '\\0';\n \t\tx->pattern = s;\n-\t\tflags = EXC_FLAG_MUSTBEDIR;\n \t} else {\n \t\tx = xmalloc(sizeof(*x));\n \t\tx->pattern = string;\n \t}\n-\tx->to_exclude = to_exclude;\n-\tx->patternlen = strlen(string);\n+\tx->patternlen = patternlen;\n+\tx->nowildcardlen = nowildcardlen;\n \tx->base = base;\n \tx->baselen = baselen;\n \tx->flags = flags;\n-\tif (!strchr(string, '/'))\n-\t\tx->flags |= EXC_FLAG_NODIR;\n-\tx->nowildcardlen = simple_length(string);\n-\tif (*string == '*' && no_wildcard(string+1))\n-\t\tx->flags |= EXC_FLAG_ENDSWITH;\n \tALLOC_GROW(which->excludes, which->nr + 1, which->alloc);\n \twhich->excludes[which->nr++] = x;\n }\n@@ -518,7 +545,7 @@ int excluded_from_list(const char *pathname,\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\tint to_exclude = x->to_exclude;\n+\t\tint to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;\n \t\tint namelen, prefix = x->nowildcardlen;\n \n \t\tif (x->flags & EXC_FLAG_MUSTBEDIR) {\ndiff --git a/dir.h b/dir.h\nindex 893465a..41ea32d 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -11,6 +11,7 @@ struct dir_entry {\n #define EXC_FLAG_NODIR 1\n #define EXC_FLAG_ENDSWITH 4\n #define EXC_FLAG_MUSTBEDIR 8\n+#define EXC_FLAG_NEGATIVE 16\n \n struct exclude_list {\n \tint nr;\n@@ -21,7 +22,6 @@ struct exclude_list {\n \t\tint nowildcardlen;\n \t\tconst char *base;\n \t\tint baselen;\n-\t\tint to_exclude;\n \t\tint flags;\n \t} **excludes;\n };\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200601","messageId":"1349412069-627-3-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 02/10] attr: avoid strlen() on every match","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:01Z","receivedAt":"2012-10-05T04:41:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n attr.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex b52efb5..2942bf6 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -277,6 +277,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n static struct attr_stack {\n \tstruct attr_stack *prev;\n \tchar *origin;\n+\tsize_t originlen;\n \tunsigned num_matches;\n \tunsigned alloc;\n \tstruct match_attr **attrs;\n@@ -532,6 +533,7 @@ static void bootstrap_attr_stack(void)\n \tif (!is_bare_repository() || direction == GIT_ATTR_INDEX) {\n \t\telem = read_attr(GITATTRIBUTES_FILE, 1);\n \t\telem->origin = xstrdup(\"\");\n+\t\telem->originlen = 0;\n \t\telem->prev = attr_stack;\n \t\tattr_stack = elem;\n \t\tdebug_push(elem);\n@@ -625,7 +627,7 @@ static void prepare_attr_stack(const char *path)\n \t\t\tstrbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);\n \t\t\telem = read_attr(pathbuf.buf, 0);\n \t\t\tstrbuf_setlen(&pathbuf, cp - path);\n-\t\t\telem->origin = strbuf_detach(&pathbuf, NULL);\n+\t\t\telem->origin = strbuf_detach(&pathbuf, &elem->originlen);\n \t\t\telem->prev = attr_stack;\n \t\t\tattr_stack = elem;\n \t\t\tdebug_push(elem);\n@@ -700,7 +702,7 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)\n \t\tif (a->is_macro)\n \t\t\tcontinue;\n \t\tif (path_matches(path, pathlen,\n-\t\t\t\t a->u.pattern, base, strlen(base)))\n+\t\t\t\t a->u.pattern, base, stk->originlen))\n \t\t\trem = fill_one(\"fill\", a, rem);\n \t}\n \treturn rem;\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200609","messageId":"1349412069-627-4-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 03/10] attr: avoid searching for basename on every match","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:02Z","receivedAt":"2012-10-05T04:41:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n attr.c | 15 +++++++++------\n 1 file changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 2942bf6..aeac564 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -644,13 +644,11 @@ static void prepare_attr_stack(const char *path)\n }\n \n static int path_matches(const char *pathname, int pathlen,\n+\t\t\tconst char *basename,\n \t\t\tconst char *pattern,\n \t\t\tconst char *base, int baselen)\n {\n \tif (!strchr(pattern, '/')) {\n-\t\t/* match basename */\n-\t\tconst char *basename = strrchr(pathname, '/');\n-\t\tbasename = basename ? basename + 1 : pathname;\n \t\treturn (fnmatch_icase(pattern, basename, 0) == 0);\n \t}\n \t/*\n@@ -692,7 +690,8 @@ static int fill_one(const char *what, struct match_attr *a, int rem)\n \treturn rem;\n }\n \n-static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)\n+static int fill(const char *path, int pathlen, const char *basename,\n+\t\tstruct attr_stack *stk, int rem)\n {\n \tint i;\n \tconst char *base = stk->origin ? stk->origin : \"\";\n@@ -701,7 +700,7 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)\n \t\tstruct match_attr *a = stk->attrs[i];\n \t\tif (a->is_macro)\n \t\t\tcontinue;\n-\t\tif (path_matches(path, pathlen,\n+\t\tif (path_matches(path, pathlen, basename,\n \t\t\t\t a->u.pattern, base, stk->originlen))\n \t\t\trem = fill_one(\"fill\", a, rem);\n \t}\n@@ -740,15 +739,19 @@ static void collect_all_attrs(const char *path)\n {\n \tstruct attr_stack *stk;\n \tint i, pathlen, rem;\n+\tconst char *basename;\n \n \tprepare_attr_stack(path);\n \tfor (i = 0; i < attr_nr; i++)\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n+\tbasename = strrchr(path, '/');\n+\tbasename = basename ? basename + 1 : path;\n+\n \tpathlen = strlen(path);\n \trem = attr_nr;\n \tfor (stk = attr_stack; 0 < rem && stk; stk = stk->prev)\n-\t\trem = fill(path, pathlen, stk, rem);\n+\t\trem = fill(path, pathlen, basename, stk, rem);\n }\n \n int git_check_attr(const char *path, int num, struct git_attr_check *check)\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200607","messageId":"1349412069-627-5-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:03Z","receivedAt":"2012-10-05T04:41:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":".gitattributes and .gitignore share the same pattern syntax but has\nseparate matching implementation. Over the years, ignore's\nimplementation accumulates more optimizations while attr's stays the\nsame.\n\nThis patch adds those optimizations to .gitattributes. Basically it\ntries to avoid fnmatch/wildmatch in favor of strncmp as much as\npossible.\n\nThere are two syntaxes that .gitignore supports but .gitattributes\ndoes not: negative patterns and directory matching. They have never\nworked and whether they will is up for future discussion. Meanwhile\nmake a note in the document and reject such patterns.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/gitattributes.txt |  2 ++\n attr.c                          | 68 ++++++++++++++++++++++++++++++++++-------\n dir.c                           |  8 ++---\n dir.h                           |  1 +\n 4 files changed, 64 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex e16f3e1..702b8c1 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -56,6 +56,8 @@ When more than one pattern matches the path, a later line\n overrides an earlier line.  This overriding is done per\n attribute.  The rules how the pattern matches paths are the\n same as in `.gitignore` files; see linkgit:gitignore[5].\n+Unlike `.gitignore`, negative patterns are not supported.\n+Patterns that match directories are also not supported.\n \n When deciding what attributes are assigned to a path, git\n consults `$GIT_DIR/info/attributes` file (which has the highest\ndiff --git a/attr.c b/attr.c\nindex aeac564..1aa058e 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -115,6 +115,13 @@ struct attr_state {\n \tconst char *setto;\n };\n \n+struct pattern {\n+\tconst char *pattern;\n+\tint patternlen;\n+\tint nowildcardlen;\n+\tint flags;\t\t/* EXC_FLAG_* */\n+};\n+\n /*\n  * One rule, as from a .gitattributes file.\n  *\n@@ -131,7 +138,7 @@ struct attr_state {\n  */\n struct match_attr {\n \tunion {\n-\t\tchar *pattern;\n+\t\tstruct pattern pat;\n \t\tstruct git_attr *attr;\n \t} u;\n \tchar is_macro;\n@@ -241,9 +248,18 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \tif (is_macro)\n \t\tres->u.attr = git_attr_internal(name, namelen);\n \telse {\n-\t\tres->u.pattern = (char *)&(res->state[num_attr]);\n-\t\tmemcpy(res->u.pattern, name, namelen);\n-\t\tres->u.pattern[namelen] = 0;\n+\t\tchar *p = (char *)&(res->state[num_attr]);\n+\t\tmemcpy(p, name, namelen);\n+\t\tp[namelen] = 0;\n+\t\tres->u.pat.pattern = p;\n+\t\tparse_exclude_pattern(&res->u.pat.pattern,\n+\t\t\t\t      &res->u.pat.patternlen,\n+\t\t\t\t      &res->u.pat.flags,\n+\t\t\t\t      &res->u.pat.nowildcardlen);\n+\t\tif (res->u.pat.flags & EXC_FLAG_NEGATIVE)\n+\t\t\tdie(_(\"Negative patterns are not supported in git attributes\"));\n+\t\tif (res->u.pat.flags & EXC_FLAG_MUSTBEDIR)\n+\t\t\tdie(_(\"Directory patterns are not supported in git attributes\"));\n \t}\n \tres->is_macro = is_macro;\n \tres->num_attr = num_attr;\n@@ -645,25 +661,55 @@ static void prepare_attr_stack(const char *path)\n \n static int path_matches(const char *pathname, int pathlen,\n \t\t\tconst char *basename,\n-\t\t\tconst char *pattern,\n+\t\t\tconst struct pattern *pat,\n \t\t\tconst char *base, int baselen)\n {\n-\tif (!strchr(pattern, '/')) {\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\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}\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+\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 * contain the trailing slash\n+\t */\n+\n \tif (pathlen < baselen ||\n \t    (baselen && pathname[baselen] != '/') ||\n \t    strncmp(pathname, base, baselen))\n \t\treturn 0;\n-\tif (baselen != 0)\n-\t\tbaselen++;\n-\treturn fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;\n+\n+\tnamelen = baselen ? pathlen - baselen - 1 : pathlen;\n+\tname = pathname + pathlen - namelen;\n+\n+\t/* if the non-wildcard part is longer than the remaining\n+\t   pathname, surely it cannot match */\n+\tif (!namelen || prefix > namelen)\n+\t\treturn 0;\n+\n+\treturn fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;\n }\n \n static int macroexpand_one(int attr_nr, int rem);\n@@ -701,7 +747,7 @@ static int fill(const char *path, int pathlen, const char *basename,\n \t\tif (a->is_macro)\n \t\t\tcontinue;\n \t\tif (path_matches(path, pathlen, basename,\n-\t\t\t\t a->u.pattern, base, stk->originlen))\n+\t\t\t\t &a->u.pat, base, stk->originlen))\n \t\t\trem = fill_one(\"fill\", a, rem);\n \t}\n \treturn rem;\ndiff --git a/dir.c b/dir.c\nindex cd13920..c6a0275 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -308,10 +308,10 @@ static int no_wildcard(const char *string)\n \treturn string[simple_length(string)] == '\\0';\n }\n \n-static void parse_exclude_pattern(const char **pattern,\n-\t\t\t\t  int *patternlen,\n-\t\t\t\t  int *flags,\n-\t\t\t\t  int *nowildcardlen)\n+void parse_exclude_pattern(const char **pattern,\n+\t\t\t   int *patternlen,\n+\t\t\t   int *flags,\n+\t\t\t   int *nowildcardlen)\n {\n \tconst char *p = *pattern;\n \tsize_t i, len;\ndiff --git a/dir.h b/dir.h\nindex 41ea32d..fd5c2aa 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -97,6 +97,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,\n extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,\n \t\t\t\t\t  char **buf_p, struct exclude_list *which, int check_index);\n extern void add_excludes_from_file(struct dir_struct *, const char *fname);\n+extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);\n extern void add_exclude(const char *string, const char *base,\n \t\t\tint baselen, struct exclude_list *which);\n extern void free_excludes(struct exclude_list *el);\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200587","messageId":"1349412069-627-6-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 05/10] Import wildmatch from rsync","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:04Z","receivedAt":"2012-10-05T04:41:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These files are from rsync.git commit\nf92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit\nbefore rsync turned GPL-3. All files are imported as-is and\nno-op. Adaptation is done in a separate patch.\n\nrsync.git           ->  git.git\nlib/wildmatch.[ch]      wildmatch.[ch]\nwildtest.c              test-wildmatch.c\nwildtest.txt            t/t3070/wildtest.txt\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t3070/wildtest.txt | 165 +++++++++++++++++++++++\n test-wildmatch.c     | 222 +++++++++++++++++++++++++++++++\n wildmatch.c          | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++\n wildmatch.h          |   6 +\n 4 files changed, 761 insertions(+)\n create mode 100644 t/t3070/wildtest.txt\n create mode 100644 test-wildmatch.c\n create mode 100644 wildmatch.c\n create mode 100644 wildmatch.h\n\ndiff --git a/t/t3070/wildtest.txt b/t/t3070/wildtest.txt\nnew file mode 100644\nindex 0000000..42c1678\n--- /dev/null\n+++ b/t/t3070/wildtest.txt\n@@ -0,0 +1,165 @@\n+# Input is in the following format (all items white-space separated):\n+#\n+# The first two items are 1 or 0 indicating if the wildmat call is expected to\n+# succeed and if fnmatch works the same way as wildmat, respectively.  After\n+# that is a text string for the match, and a pattern string.  Strings can be\n+# quoted (if desired) in either double or single quotes, as well as backticks.\n+#\n+# MATCH FNMATCH_SAME \"text to match\" 'pattern to use'\n+\n+# Basic wildmat features\n+1 1 foo\t\t\tfoo\n+0 1 foo\t\t\tbar\n+1 1 ''\t\t\t\"\"\n+1 1 foo\t\t\t???\n+0 1 foo\t\t\t??\n+1 1 foo\t\t\t*\n+1 1 foo\t\t\tf*\n+0 1 foo\t\t\t*f\n+1 1 foo\t\t\t*foo*\n+1 1 foobar\t\t*ob*a*r*\n+1 1 aaaaaaabababab\t*ab\n+1 1 foo*\t\tfoo\\*\n+0 1 foobar\t\tfoo\\*bar\n+1 1 f\\oo\t\tf\\\\oo\n+1 1 ball\t\t*[al]?\n+0 1 ten\t\t\t[ten]\n+1 1 ten\t\t\t**[!te]\n+0 1 ten\t\t\t**[!ten]\n+1 1 ten\t\t\tt[a-g]n\n+0 1 ten\t\t\tt[!a-g]n\n+1 1 ton\t\t\tt[!a-g]n\n+1 1 ton\t\t\tt[^a-g]n\n+1 1 a]b\t\t\ta[]]b\n+1 1 a-b\t\t\ta[]-]b\n+1 1 a]b\t\t\ta[]-]b\n+0 1 aab\t\t\ta[]-]b\n+1 1 aab\t\t\ta[]a-]b\n+1 1 ]\t\t\t]\n+\n+# Extended slash-matching features\n+0 1 foo/baz/bar\t\tfoo*bar\n+1 1 foo/baz/bar\t\tfoo**bar\n+0 1 foo/bar\t\tfoo?bar\n+0 1 foo/bar\t\tfoo[/]bar\n+0 1 foo/bar\t\tf[^eiu][^eiu][^eiu][^eiu][^eiu]r\n+1 1 foo-bar\t\tf[^eiu][^eiu][^eiu][^eiu][^eiu]r\n+0 1 foo\t\t\t**/foo\n+1 1 /foo\t\t**/foo\n+1 1 bar/baz/foo\t\t**/foo\n+0 1 bar/baz/foo\t\t*/foo\n+0 0 foo/bar/baz\t\t**/bar*\n+1 1 deep/foo/bar/baz\t**/bar/*\n+0 1 deep/foo/bar/baz/\t**/bar/*\n+1 1 deep/foo/bar/baz/\t**/bar/**\n+0 1 deep/foo/bar\t**/bar/*\n+1 1 deep/foo/bar/\t**/bar/**\n+1 1 foo/bar/baz\t\t**/bar**\n+1 1 foo/bar/baz/x\t*/bar/**\n+0 0 deep/foo/bar/baz/x\t*/bar/**\n+1 1 deep/foo/bar/baz/x\t**/bar/*/*\n+\n+# Various additional tests\n+0 1 acrt\t\ta[c-c]st\n+1 1 acrt\t\ta[c-c]rt\n+0 1 ]\t\t\t[!]-]\n+1 1 a\t\t\t[!]-]\n+0 1 ''\t\t\t\\\n+0 1 \\\t\t\t\\\n+0 1 /\\\t\t\t*/\\\n+1 1 /\\\t\t\t*/\\\\\n+1 1 foo\t\t\tfoo\n+1 1 @foo\t\t@foo\n+0 1 foo\t\t\t@foo\n+1 1 [ab]\t\t\\[ab]\n+1 1 [ab]\t\t[[]ab]\n+1 1 [ab]\t\t[[:]ab]\n+0 1 [ab]\t\t[[::]ab]\n+1 1 [ab]\t\t[[:digit]ab]\n+1 1 [ab]\t\t[\\[:]ab]\n+1 1 ?a?b\t\t\\??\\?b\n+1 1 abc\t\t\t\\a\\b\\c\n+0 1 foo\t\t\t''\n+1 1 foo/bar/baz/to\t**/t[o]\n+\n+# Character class tests\n+1 1 a1B\t\t[[:alpha:]][[:digit:]][[:upper:]]\n+0 1 a\t\t[[:digit:][:upper:][:space:]]\n+1 1 A\t\t[[:digit:][:upper:][:space:]]\n+1 1 1\t\t[[:digit:][:upper:][:space:]]\n+0 1 1\t\t[[:digit:][:upper:][:spaci:]]\n+1 1 ' '\t\t[[:digit:][:upper:][:space:]]\n+0 1 .\t\t[[:digit:][:upper:][:space:]]\n+1 1 .\t\t[[:digit:][:punct:][:space:]]\n+1 1 5\t\t[[:xdigit:]]\n+1 1 f\t\t[[:xdigit:]]\n+1 1 D\t\t[[:xdigit:]]\n+1 1 _\t\t[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]\n+#1 1 �\t\t[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]\n+1 1 \t\t[^[:alnum:][:alpha:][:blank:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]\n+1 1 .\t\t[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]\n+1 1 5\t\t[a-c[:digit:]x-z]\n+1 1 b\t\t[a-c[:digit:]x-z]\n+1 1 y\t\t[a-c[:digit:]x-z]\n+0 1 q\t\t[a-c[:digit:]x-z]\n+\n+# Additional tests, including some malformed wildmats\n+1 1 ]\t\t[\\\\-^]\n+0 1 [\t\t[\\\\-^]\n+1 1 -\t\t[\\-_]\n+1 1 ]\t\t[\\]]\n+0 1 \\]\t\t[\\]]\n+0 1 \\\t\t[\\]]\n+0 1 ab\t\ta[]b\n+0 1 a[]b\ta[]b\n+0 1 ab[\t\tab[\n+0 1 ab\t\t[!\n+0 1 ab\t\t[-\n+1 1 -\t\t[-]\n+0 1 -\t\t[a-\n+0 1 -\t\t[!a-\n+1 1 -\t\t[--A]\n+1 1 5\t\t[--A]\n+1 1 ' '\t\t'[ --]'\n+1 1 $\t\t'[ --]'\n+1 1 -\t\t'[ --]'\n+0 1 0\t\t'[ --]'\n+1 1 -\t\t[---]\n+1 1 -\t\t[------]\n+0 1 j\t\t[a-e-n]\n+1 1 -\t\t[a-e-n]\n+1 1 a\t\t[!------]\n+0 1 [\t\t[]-a]\n+1 1 ^\t\t[]-a]\n+0 1 ^\t\t[!]-a]\n+1 1 [\t\t[!]-a]\n+1 1 ^\t\t[a^bc]\n+1 1 -b]\t\t[a-]b]\n+0 1 \\\t\t[\\]\n+1 1 \\\t\t[\\\\]\n+0 1 \\\t\t[!\\\\]\n+1 1 G\t\t[A-\\\\]\n+0 1 aaabbb\tb*a\n+0 1 aabcaa\t*ba*\n+1 1 ,\t\t[,]\n+1 1 ,\t\t[\\\\,]\n+1 1 \\\t\t[\\\\,]\n+1 1 -\t\t[,-.]\n+0 1 +\t\t[,-.]\n+0 1 -.]\t\t[,-.]\n+1 1 2\t\t[\\1-\\3]\n+1 1 3\t\t[\\1-\\3]\n+0 1 4\t\t[\\1-\\3]\n+1 1 \\\t\t[[-\\]]\n+1 1 [\t\t[[-\\]]\n+1 1 ]\t\t[[-\\]]\n+0 1 -\t\t[[-\\]]\n+\n+# Test recursion and the abort code (use \"wildtest -i\" to see iteration counts)\n+1 1 -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1\t-*-*-*-*-*-*-12-*-*-*-m-*-*-*\n+0 1 -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1\t-*-*-*-*-*-*-12-*-*-*-m-*-*-*\n+0 1 -adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1\t-*-*-*-*-*-*-12-*-*-*-m-*-*-*\n+1 1 /adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1\t/*/*/*/*/*/*/12/*/*/*/m/*/*/*\n+0 1 /adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1\t/*/*/*/*/*/*/12/*/*/*/m/*/*/*\n+1 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt\t\t**/*a*b*g*n*t\n+0 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz\t\t**/*a*b*g*n*t\ndiff --git a/test-wildmatch.c b/test-wildmatch.c\nnew file mode 100644\nindex 0000000..88585c2\n--- /dev/null\n+++ b/test-wildmatch.c\n@@ -0,0 +1,222 @@\n+/*\n+ * Test suite for the wildmatch code.\n+ *\n+ * Copyright (C) 2003-2009 Wayne Davison\n+ *\n+ * This program is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation; either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License along\n+ * with this program; if not, visit the http://fsf.org website.\n+ */\n+\n+/*#define COMPARE_WITH_FNMATCH*/\n+\n+#define WILD_TEST_ITERATIONS\n+#include \"lib/wildmatch.c\"\n+\n+#include <popt.h>\n+\n+#ifdef COMPARE_WITH_FNMATCH\n+#include <fnmatch.h>\n+\n+int fnmatch_errors = 0;\n+#endif\n+\n+int wildmatch_errors = 0;\n+char number_separator = ',';\n+\n+typedef char bool;\n+\n+int output_iterations = 0;\n+int explode_mod = 0;\n+int empties_mod = 0;\n+int empty_at_start = 0;\n+int empty_at_end = 0;\n+\n+static struct poptOption long_options[] = {\n+  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */\n+  {\"iterations\",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},\n+  {\"empties\",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},\n+  {\"explode\",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},\n+  {0,0,0,0, 0, 0, 0}\n+};\n+\n+/* match just at the start of string (anchored tests) */\n+static void\n+run_test(int line, bool matches,\n+#ifdef COMPARE_WITH_FNMATCH\n+\t bool same_as_fnmatch,\n+#endif\n+\t const char *text, const char *pattern)\n+{\n+    bool matched;\n+#ifdef COMPARE_WITH_FNMATCH\n+    bool fn_matched;\n+    int flags = strstr(pattern, \"**\")? 0 : FNM_PATHNAME;\n+#endif\n+\n+    if (explode_mod) {\n+\tchar buf[MAXPATHLEN*2], *texts[MAXPATHLEN];\n+\tint pos = 0, cnt = 0, ndx = 0, len = strlen(text);\n+\n+\tif (empty_at_start)\n+\t    texts[ndx++] = \"\";\n+\t/* An empty string must turn into at least one empty array item. */\n+\twhile (1) {\n+\t    texts[ndx] = buf + ndx * (explode_mod + 1);\n+\t    strlcpy(texts[ndx++], text + pos, explode_mod + 1);\n+\t    if (pos + explode_mod >= len)\n+\t\tbreak;\n+\t    pos += explode_mod;\n+\t    if (!(++cnt % empties_mod))\n+\t\ttexts[ndx++] = \"\";\n+\t}\n+\tif (empty_at_end)\n+\t    texts[ndx++] = \"\";\n+\ttexts[ndx] = NULL;\n+\tmatched = wildmatch_array(pattern, (const char**)texts, 0);\n+    } else\n+\tmatched = wildmatch(pattern, text);\n+#ifdef COMPARE_WITH_FNMATCH\n+    fn_matched = !fnmatch(pattern, text, flags);\n+#endif\n+    if (matched != matches) {\n+\tprintf(\"wildmatch failure on line %d:\\n  %s\\n  %s\\n  expected %s match\\n\",\n+\t       line, text, pattern, matches? \"a\" : \"NO\");\n+\twildmatch_errors++;\n+    }\n+#ifdef COMPARE_WITH_FNMATCH\n+    if (fn_matched != (matches ^ !same_as_fnmatch)) {\n+\tprintf(\"fnmatch disagreement on line %d:\\n  %s\\n  %s\\n  expected %s match\\n\",\n+\t       line, text, pattern, matches ^ !same_as_fnmatch? \"a\" : \"NO\");\n+\tfnmatch_errors++;\n+    }\n+#endif\n+    if (output_iterations) {\n+\tprintf(\"%d: \\\"%s\\\" iterations = %d\\n\", line, pattern,\n+\t       wildmatch_iteration_count);\n+    }\n+}\n+\n+int\n+main(int argc, char **argv)\n+{\n+    char buf[2048], *s, *string[2], *end[2];\n+    const char *arg;\n+    FILE *fp;\n+    int opt, line, i, flag[2];\n+    poptContext pc = poptGetContext(\"wildtest\", argc, (const char**)argv,\n+\t\t\t\t    long_options, 0);\n+\n+    while ((opt = poptGetNextOpt(pc)) != -1) {\n+\tswitch (opt) {\n+\t  case 'e':\n+\t    arg = poptGetOptArg(pc);\n+\t    empties_mod = atoi(arg);\n+\t    if (strchr(arg, 's'))\n+\t\tempty_at_start = 1;\n+\t    if (strchr(arg, 'e'))\n+\t\tempty_at_end = 1;\n+\t    if (!explode_mod)\n+\t\texplode_mod = 1024;\n+\t    break;\n+\t  default:\n+\t    fprintf(stderr, \"%s: %s\\n\",\n+\t\t    poptBadOption(pc, POPT_BADOPTION_NOALIAS),\n+\t\t    poptStrerror(opt));\n+\t    exit(1);\n+\t}\n+    }\n+\n+    if (explode_mod && !empties_mod)\n+\tempties_mod = 1024;\n+\n+    argv = (char**)poptGetArgs(pc);\n+    if (!argv || argv[1]) {\n+\tfprintf(stderr, \"Usage: wildtest [OPTIONS] TESTFILE\\n\");\n+\texit(1);\n+    }\n+\n+    if ((fp = fopen(*argv, \"r\")) == NULL) {\n+\tfprintf(stderr, \"Unable to open %s\\n\", *argv);\n+\texit(1);\n+    }\n+\n+    line = 0;\n+    while (fgets(buf, sizeof buf, fp)) {\n+\tline++;\n+\tif (*buf == '#' || *buf == '\\n')\n+\t    continue;\n+\tfor (s = buf, i = 0; i <= 1; i++) {\n+\t    if (*s == '1')\n+\t\tflag[i] = 1;\n+\t    else if (*s == '0')\n+\t\tflag[i] = 0;\n+\t    else\n+\t\tflag[i] = -1;\n+\t    if (*++s != ' ' && *s != '\\t')\n+\t\tflag[i] = -1;\n+\t    if (flag[i] < 0) {\n+\t\tfprintf(stderr, \"Invalid flag syntax on line %d of %s:\\n%s\",\n+\t\t\tline, *argv, buf);\n+\t\texit(1);\n+\t    }\n+\t    while (*++s == ' ' || *s == '\\t') {}\n+\t}\n+\tfor (i = 0; i <= 1; i++) {\n+\t    if (*s == '\\'' || *s == '\"' || *s == '`') {\n+\t\tchar quote = *s++;\n+\t\tstring[i] = s;\n+\t\twhile (*s && *s != quote) s++;\n+\t\tif (!*s) {\n+\t\t    fprintf(stderr, \"Unmatched quote on line %d of %s:\\n%s\",\n+\t\t\t    line, *argv, buf);\n+\t\t    exit(1);\n+\t\t}\n+\t\tend[i] = s;\n+\t    }\n+\t    else {\n+\t\tif (!*s || *s == '\\n') {\n+\t\t    fprintf(stderr, \"Not enough strings on line %d of %s:\\n%s\",\n+\t\t\t    line, *argv, buf);\n+\t\t    exit(1);\n+\t\t}\n+\t\tstring[i] = s;\n+\t\twhile (*++s && *s != ' ' && *s != '\\t' && *s != '\\n') {}\n+\t\tend[i] = s;\n+\t    }\n+\t    while (*++s == ' ' || *s == '\\t') {}\n+\t}\n+\t*end[0] = *end[1] = '\\0';\n+\trun_test(line, flag[0],\n+#ifdef COMPARE_WITH_FNMATCH\n+\t\t flag[1],\n+#endif\n+\t\t string[0], string[1]);\n+    }\n+\n+    if (!wildmatch_errors)\n+\tfputs(\"No\", stdout);\n+    else\n+\tprintf(\"%d\", wildmatch_errors);\n+    printf(\" wildmatch error%s found.\\n\", wildmatch_errors == 1? \"\" : \"s\");\n+\n+#ifdef COMPARE_WITH_FNMATCH\n+    if (!fnmatch_errors)\n+\tfputs(\"No\", stdout);\n+    else\n+\tprintf(\"%d\", fnmatch_errors);\n+    printf(\" fnmatch error%s found.\\n\", fnmatch_errors == 1? \"\" : \"s\");\n+\n+#endif\n+\n+    return 0;\n+}\ndiff --git a/wildmatch.c b/wildmatch.c\nnew file mode 100644\nindex 0000000..f3a1731\n--- /dev/null\n+++ b/wildmatch.c\n@@ -0,0 +1,368 @@\n+/*\n+**  Do shell-style pattern matching for ?, \\, [], and * characters.\n+**  It is 8bit clean.\n+**\n+**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.\n+**  Rich $alz is now <rsalz@bbn.com>.\n+**\n+**  Modified by Wayne Davison to special-case '/' matching, to make '**'\n+**  work differently than '*', and to fix the character-class code.\n+*/\n+\n+#include \"rsync.h\"\n+\n+/* What character marks an inverted character class? */\n+#define NEGATE_CLASS\t'!'\n+#define NEGATE_CLASS2\t'^'\n+\n+#define FALSE 0\n+#define TRUE 1\n+#define ABORT_ALL -1\n+#define ABORT_TO_STARSTAR -2\n+\n+#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \\\n+\t\t\t\t    && *(class) == *(litmatch) \\\n+\t\t\t\t    && strncmp((char*)class, litmatch, len) == 0)\n+\n+#if defined STDC_HEADERS || !defined isascii\n+# define ISASCII(c) 1\n+#else\n+# define ISASCII(c) isascii(c)\n+#endif\n+\n+#ifdef isblank\n+# define ISBLANK(c) (ISASCII(c) && isblank(c))\n+#else\n+# define ISBLANK(c) ((c) == ' ' || (c) == '\\t')\n+#endif\n+\n+#ifdef isgraph\n+# define ISGRAPH(c) (ISASCII(c) && isgraph(c))\n+#else\n+# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))\n+#endif\n+\n+#define ISPRINT(c) (ISASCII(c) && isprint(c))\n+#define ISDIGIT(c) (ISASCII(c) && isdigit(c))\n+#define ISALNUM(c) (ISASCII(c) && isalnum(c))\n+#define ISALPHA(c) (ISASCII(c) && isalpha(c))\n+#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))\n+#define ISLOWER(c) (ISASCII(c) && islower(c))\n+#define ISPUNCT(c) (ISASCII(c) && ispunct(c))\n+#define ISSPACE(c) (ISASCII(c) && isspace(c))\n+#define ISUPPER(c) (ISASCII(c) && isupper(c))\n+#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))\n+\n+#ifdef WILD_TEST_ITERATIONS\n+int wildmatch_iteration_count;\n+#endif\n+\n+static int force_lower_case = 0;\n+\n+/* Match pattern \"p\" against the a virtually-joined string consisting\n+ * of \"text\" and any strings in array \"a\". */\n+static int dowild(const uchar *p, const uchar *text, const uchar*const *a)\n+{\n+    uchar p_ch;\n+\n+#ifdef WILD_TEST_ITERATIONS\n+    wildmatch_iteration_count++;\n+#endif\n+\n+    for ( ; (p_ch = *p) != '\\0'; text++, p++) {\n+\tint matched, special;\n+\tuchar t_ch, prev_ch;\n+\twhile ((t_ch = *text) == '\\0') {\n+\t    if (*a == NULL) {\n+\t\tif (p_ch != '*')\n+\t\t    return ABORT_ALL;\n+\t\tbreak;\n+\t    }\n+\t    text = *a++;\n+\t}\n+\tif (force_lower_case && ISUPPER(t_ch))\n+\t    t_ch = tolower(t_ch);\n+\tswitch (p_ch) {\n+\t  case '\\\\':\n+\t    /* Literal match with following character.  Note that the test\n+\t     * in \"default\" handles the p[1] == '\\0' failure case. */\n+\t    p_ch = *++p;\n+\t    /* FALLTHROUGH */\n+\t  default:\n+\t    if (t_ch != p_ch)\n+\t\treturn FALSE;\n+\t    continue;\n+\t  case '?':\n+\t    /* Match anything but '/'. */\n+\t    if (t_ch == '/')\n+\t\treturn FALSE;\n+\t    continue;\n+\t  case '*':\n+\t    if (*++p == '*') {\n+\t\twhile (*++p == '*') {}\n+\t\tspecial = TRUE;\n+\t    } else\n+\t\tspecial = FALSE;\n+\t    if (*p == '\\0') {\n+\t\t/* Trailing \"**\" matches everything.  Trailing \"*\" matches\n+\t\t * only if there are no more slash characters. */\n+\t\tif (!special) {\n+\t\t    do {\n+\t\t\tif (strchr((char*)text, '/') != NULL)\n+\t\t\t    return FALSE;\n+\t\t    } while ((text = *a++) != NULL);\n+\t\t}\n+\t\treturn TRUE;\n+\t    }\n+\t    while (1) {\n+\t\tif (t_ch == '\\0') {\n+\t\t    if ((text = *a++) == NULL)\n+\t\t\tbreak;\n+\t\t    t_ch = *text;\n+\t\t    continue;\n+\t\t}\n+\t\tif ((matched = dowild(p, text, a)) != FALSE) {\n+\t\t    if (!special || matched != ABORT_TO_STARSTAR)\n+\t\t\treturn matched;\n+\t\t} else if (!special && t_ch == '/')\n+\t\t    return ABORT_TO_STARSTAR;\n+\t\tt_ch = *++text;\n+\t    }\n+\t    return ABORT_ALL;\n+\t  case '[':\n+\t    p_ch = *++p;\n+#ifdef NEGATE_CLASS2\n+\t    if (p_ch == NEGATE_CLASS2)\n+\t\tp_ch = NEGATE_CLASS;\n+#endif\n+\t    /* Assign literal TRUE/FALSE because of \"matched\" comparison. */\n+\t    special = p_ch == NEGATE_CLASS? TRUE : FALSE;\n+\t    if (special) {\n+\t\t/* Inverted character class. */\n+\t\tp_ch = *++p;\n+\t    }\n+\t    prev_ch = 0;\n+\t    matched = FALSE;\n+\t    do {\n+\t\tif (!p_ch)\n+\t\t    return ABORT_ALL;\n+\t\tif (p_ch == '\\\\') {\n+\t\t    p_ch = *++p;\n+\t\t    if (!p_ch)\n+\t\t\treturn ABORT_ALL;\n+\t\t    if (t_ch == p_ch)\n+\t\t\tmatched = TRUE;\n+\t\t} else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {\n+\t\t    p_ch = *++p;\n+\t\t    if (p_ch == '\\\\') {\n+\t\t\tp_ch = *++p;\n+\t\t\tif (!p_ch)\n+\t\t\t    return ABORT_ALL;\n+\t\t    }\n+\t\t    if (t_ch <= p_ch && t_ch >= prev_ch)\n+\t\t\tmatched = TRUE;\n+\t\t    p_ch = 0; /* This makes \"prev_ch\" get set to 0. */\n+\t\t} else if (p_ch == '[' && p[1] == ':') {\n+\t\t    const uchar *s;\n+\t\t    int i;\n+\t\t    for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/\n+\t\t    if (!p_ch)\n+\t\t\treturn ABORT_ALL;\n+\t\t    i = p - s - 1;\n+\t\t    if (i < 0 || p[-1] != ':') {\n+\t\t\t/* Didn't find \":]\", so treat like a normal set. */\n+\t\t\tp = s - 2;\n+\t\t\tp_ch = '[';\n+\t\t\tif (t_ch == p_ch)\n+\t\t\t    matched = TRUE;\n+\t\t\tcontinue;\n+\t\t    }\n+\t\t    if (CC_EQ(s,i, \"alnum\")) {\n+\t\t\tif (ISALNUM(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"alpha\")) {\n+\t\t\tif (ISALPHA(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"blank\")) {\n+\t\t\tif (ISBLANK(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"cntrl\")) {\n+\t\t\tif (ISCNTRL(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"digit\")) {\n+\t\t\tif (ISDIGIT(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"graph\")) {\n+\t\t\tif (ISGRAPH(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"lower\")) {\n+\t\t\tif (ISLOWER(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"print\")) {\n+\t\t\tif (ISPRINT(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"punct\")) {\n+\t\t\tif (ISPUNCT(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"space\")) {\n+\t\t\tif (ISSPACE(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"upper\")) {\n+\t\t\tif (ISUPPER(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else if (CC_EQ(s,i, \"xdigit\")) {\n+\t\t\tif (ISXDIGIT(t_ch))\n+\t\t\t    matched = TRUE;\n+\t\t    } else /* malformed [:class:] string */\n+\t\t\treturn ABORT_ALL;\n+\t\t    p_ch = 0; /* This makes \"prev_ch\" get set to 0. */\n+\t\t} else if (t_ch == p_ch)\n+\t\t    matched = TRUE;\n+\t    } while (prev_ch = p_ch, (p_ch = *++p) != ']');\n+\t    if (matched == special || t_ch == '/')\n+\t\treturn FALSE;\n+\t    continue;\n+\t}\n+    }\n+\n+    do {\n+\tif (*text)\n+\t    return FALSE;\n+    } while ((text = *a++) != NULL);\n+\n+    return TRUE;\n+}\n+\n+/* Match literal string \"s\" against the a virtually-joined string consisting\n+ * of \"text\" and any strings in array \"a\". */\n+static int doliteral(const uchar *s, const uchar *text, const uchar*const *a)\n+{\n+    for ( ; *s != '\\0'; text++, s++) {\n+\twhile (*text == '\\0') {\n+\t    if ((text = *a++) == NULL)\n+\t\treturn FALSE;\n+\t}\n+\tif (*text != *s)\n+\t    return FALSE;\n+    }\n+\n+    do {\n+\tif (*text)\n+\t    return FALSE;\n+    } while ((text = *a++) != NULL);\n+\n+    return TRUE;\n+}\n+\n+/* Return the last \"count\" path elements from the concatenated string.\n+ * We return a string pointer to the start of the string, and update the\n+ * array pointer-pointer to point to any remaining string elements. */\n+static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)\n+{\n+    const uchar*const *a = *a_ptr;\n+    const uchar*const *first_a = a;\n+\n+    while (*a)\n+\t    a++;\n+\n+    while (a != first_a) {\n+\tconst uchar *s = *--a;\n+\ts += strlen((char*)s);\n+\twhile (--s >= *a) {\n+\t    if (*s == '/' && !--count) {\n+\t\t*a_ptr = a+1;\n+\t\treturn s+1;\n+\t    }\n+\t}\n+    }\n+\n+    if (count == 1) {\n+\t*a_ptr = a+1;\n+\treturn *a;\n+    }\n+\n+    return NULL;\n+}\n+\n+/* Match the \"pattern\" against the \"text\" string. */\n+int wildmatch(const char *pattern, const char *text)\n+{\n+    static const uchar *nomore[1]; /* A NULL pointer. */\n+#ifdef WILD_TEST_ITERATIONS\n+    wildmatch_iteration_count = 0;\n+#endif\n+    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;\n+}\n+\n+/* Match the \"pattern\" against the forced-to-lower-case \"text\" string. */\n+int iwildmatch(const char *pattern, const char *text)\n+{\n+    static const uchar *nomore[1]; /* A NULL pointer. */\n+    int ret;\n+#ifdef WILD_TEST_ITERATIONS\n+    wildmatch_iteration_count = 0;\n+#endif\n+    force_lower_case = 1;\n+    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;\n+    force_lower_case = 0;\n+    return ret;\n+}\n+\n+/* Match pattern \"p\" against the a virtually-joined string consisting\n+ * of all the pointers in array \"texts\" (which has a NULL pointer at the\n+ * end).  The int \"where\" can be 0 (normal matching), > 0 (match only\n+ * the trailing N slash-separated filename components of \"texts\"), or < 0\n+ * (match the \"pattern\" at the start or after any slash in \"texts\"). */\n+int wildmatch_array(const char *pattern, const char*const *texts, int where)\n+{\n+    const uchar *p = (const uchar*)pattern;\n+    const uchar*const *a = (const uchar*const*)texts;\n+    const uchar *text;\n+    int matched;\n+\n+#ifdef WILD_TEST_ITERATIONS\n+    wildmatch_iteration_count = 0;\n+#endif\n+\n+    if (where > 0)\n+\ttext = trailing_N_elements(&a, where);\n+    else\n+\ttext = *a++;\n+    if (!text)\n+\treturn FALSE;\n+\n+    if ((matched = dowild(p, text, a)) != TRUE && where < 0\n+     && matched != ABORT_ALL) {\n+\twhile (1) {\n+\t    if (*text == '\\0') {\n+\t\tif ((text = (uchar*)*a++) == NULL)\n+\t\t    return FALSE;\n+\t\tcontinue;\n+\t    }\n+\t    if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE\n+\t     && matched != ABORT_TO_STARSTAR)\n+\t\tbreak;\n+\t}\n+    }\n+    return matched == TRUE;\n+}\n+\n+/* Match literal string \"s\" against the a virtually-joined string consisting\n+ * of all the pointers in array \"texts\" (which has a NULL pointer at the\n+ * end).  The int \"where\" can be 0 (normal matching), or > 0 (match\n+ * only the trailing N slash-separated filename components of \"texts\"). */\n+int litmatch_array(const char *string, const char*const *texts, int where)\n+{\n+    const uchar *s = (const uchar*)string;\n+    const uchar*const *a = (const uchar* const*)texts;\n+    const uchar *text;\n+\n+    if (where > 0)\n+\ttext = trailing_N_elements(&a, where);\n+    else\n+\ttext = *a++;\n+    if (!text)\n+\treturn FALSE;\n+\n+    return doliteral(s, text, a) == TRUE;\n+}\ndiff --git a/wildmatch.h b/wildmatch.h\nnew file mode 100644\nindex 0000000..e7f1a35\n--- /dev/null\n+++ b/wildmatch.h\n@@ -0,0 +1,6 @@\n+/* wildmatch.h */\n+\n+int wildmatch(const char *pattern, const char *text);\n+int iwildmatch(const char *pattern, const char *text);\n+int wildmatch_array(const char *pattern, const char*const *texts, int where);\n+int litmatch_array(const char *string, const char*const *texts, int where);\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200608","messageId":"1349412069-627-7-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 06/10] wildmatch: remove static variable force_lower_case","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:05Z","receivedAt":"2012-10-05T04:41:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"One place less to worry about thread safety\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n wildmatch.c | 17 +++++++----------\n 1 file changed, 7 insertions(+), 10 deletions(-)\n\ndiff --git a/wildmatch.c b/wildmatch.c\nindex f3a1731..e824eb2 100644\n--- a/wildmatch.c\n+++ b/wildmatch.c\n@@ -57,11 +57,10 @@\n int wildmatch_iteration_count;\n #endif\n \n-static int force_lower_case = 0;\n-\n /* Match pattern \"p\" against the a virtually-joined string consisting\n  * of \"text\" and any strings in array \"a\". */\n-static int dowild(const uchar *p, const uchar *text, const uchar*const *a)\n+static int dowild(const uchar *p, const uchar *text,\n+\t\t  const uchar*const *a, int force_lower_case)\n {\n     uchar p_ch;\n \n@@ -121,7 +120,7 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)\n \t\t    t_ch = *text;\n \t\t    continue;\n \t\t}\n-\t\tif ((matched = dowild(p, text, a)) != FALSE) {\n+\t\tif ((matched = dowild(p, text, a, force_lower_case)) != FALSE) {\n \t\t    if (!special || matched != ABORT_TO_STARSTAR)\n \t\t\treturn matched;\n \t\t} else if (!special && t_ch == '/')\n@@ -291,7 +290,7 @@ int wildmatch(const char *pattern, const char *text)\n #ifdef WILD_TEST_ITERATIONS\n     wildmatch_iteration_count = 0;\n #endif\n-    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;\n+    return dowild((const uchar*)pattern, (const uchar*)text, nomore, 0) == TRUE;\n }\n \n /* Match the \"pattern\" against the forced-to-lower-case \"text\" string. */\n@@ -302,9 +301,7 @@ int iwildmatch(const char *pattern, const char *text)\n #ifdef WILD_TEST_ITERATIONS\n     wildmatch_iteration_count = 0;\n #endif\n-    force_lower_case = 1;\n-    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;\n-    force_lower_case = 0;\n+    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore, 1) == TRUE;\n     return ret;\n }\n \n@@ -331,7 +328,7 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)\n     if (!text)\n \treturn FALSE;\n \n-    if ((matched = dowild(p, text, a)) != TRUE && where < 0\n+    if ((matched = dowild(p, text, a, 0)) != TRUE && where < 0\n      && matched != ABORT_ALL) {\n \twhile (1) {\n \t    if (*text == '\\0') {\n@@ -339,7 +336,7 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)\n \t\t    return FALSE;\n \t\tcontinue;\n \t    }\n-\t    if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE\n+\t    if (*text++ == '/' && (matched = dowild(p, text, a, 0)) != FALSE\n \t     && matched != ABORT_TO_STARSTAR)\n \t\tbreak;\n \t}\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200599","messageId":"1349412069-627-8-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 07/10] wildmatch: fix case-insensitive matching","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:06Z","receivedAt":"2012-10-05T04:41:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"dowild() does case insensitive matching by lower-casing the text. That\nmeans lower case letters in patterns imply case-insensitive matching,\nbut upper case means exact matching.\n\nWe do not want that subtlety. Lower case pattern too so iwildmatch()\nalways does what we expect it to do.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n wildmatch.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/wildmatch.c b/wildmatch.c\nindex e824eb2..c7f7f9f 100644\n--- a/wildmatch.c\n+++ b/wildmatch.c\n@@ -81,6 +81,8 @@ static int dowild(const uchar *p, const uchar *text,\n \t}\n \tif (force_lower_case && ISUPPER(t_ch))\n \t    t_ch = tolower(t_ch);\n+\tif (force_lower_case && ISUPPER(p_ch))\n+\t    p_ch = tolower(p_ch);\n \tswitch (p_ch) {\n \t  case '\\\\':\n \t    /* Literal match with following character.  Note that the test\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200604","messageId":"1349412069-627-9-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 08/10] Integrate wildmatch to git","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:07Z","receivedAt":"2012-10-05T04:41:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This makes wildmatch.c part of libgit.a and builds test-wildmatch; the\ndependency on libpopt in the original has been replaced with the use\nof our parse-options. Global variables in test-wildmatch are marked\nstatic to avoid sparse warnings.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore           |  1 +\n Makefile             |  3 ++\n t/t3070-wildmatch.sh | 27 ++++++++++++++++\n test-wildmatch.c     | 88 ++++++++++++++++++++++------------------------------\n wildmatch.c          | 26 +++++-----------\n 5 files changed, 75 insertions(+), 70 deletions(-)\n create mode 100755 t/t3070-wildmatch.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 68fe464..54b1b3b 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -196,6 +196,7 @@\n /test-sigchain\n /test-subprocess\n /test-svn-fe\n+/test-wildmatch\n /common-cmds.h\n *.tar.gz\n *.dsc\ndiff --git a/Makefile b/Makefile\nindex 26b697d..d6235e6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -504,6 +504,7 @@ TEST_PROGRAMS_NEED_X += test-sha1\n TEST_PROGRAMS_NEED_X += test-sigchain\n TEST_PROGRAMS_NEED_X += test-subprocess\n TEST_PROGRAMS_NEED_X += test-svn-fe\n+TEST_PROGRAMS_NEED_X += test-wildmatch\n \n TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))\n \n@@ -676,6 +677,7 @@ LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += varint.h\n LIB_H += walker.h\n+LIB_H += wildmatch.h\n LIB_H += wt-status.h\n LIB_H += xdiff-interface.h\n LIB_H += xdiff/xdiff.h\n@@ -807,6 +809,7 @@ LIB_OBJS += utf8.o\n LIB_OBJS += varint.o\n LIB_OBJS += version.o\n LIB_OBJS += walker.o\n+LIB_OBJS += wildmatch.o\n LIB_OBJS += wrapper.o\n LIB_OBJS += write_or_die.o\n LIB_OBJS += ws.o\ndiff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh\nnew file mode 100755\nindex 0000000..c4da26c\n--- /dev/null\n+++ b/t/t3070-wildmatch.sh\n@@ -0,0 +1,27 @@\n+#!/bin/sh\n+\n+test_description='wildmatch tests'\n+\n+. ./test-lib.sh\n+\n+test_wildmatch() {\n+    test_expect_success \"wildmatch $*\" \"\n+\ttest-wildmatch $* ../t3070/wildtest.txt >actual &&\n+\techo 'No wildmatch errors found.' >expected &&\n+\ttest_cmp expected actual\n+    \"\n+}\n+\n+test_wildmatch -x1\n+test_wildmatch -x1 -e1\n+test_wildmatch -x1 -else\n+test_wildmatch -x2\n+test_wildmatch -x2 -ese\n+test_wildmatch -x3\n+test_wildmatch -x3 -e1\n+test_wildmatch -x4\n+test_wildmatch -x4 -e2e\n+test_wildmatch -x5\n+test_wildmatch -x5 -es\n+\n+test_done\ndiff --git a/test-wildmatch.c b/test-wildmatch.c\nindex 88585c2..bb726c8 100644\n--- a/test-wildmatch.c\n+++ b/test-wildmatch.c\n@@ -19,34 +19,38 @@\n \n /*#define COMPARE_WITH_FNMATCH*/\n \n-#define WILD_TEST_ITERATIONS\n-#include \"lib/wildmatch.c\"\n+#include \"cache.h\"\n+#include \"parse-options.h\"\n+#include \"wildmatch.h\"\n \n-#include <popt.h>\n+#ifndef MAXPATHLEN\n+#define MAXPATHLEN 1024\n+#endif\n+#ifdef NO_STRLCPY\n+#include \"compat/strlcpy.c\"\n+#define strlcpy gitstrlcpy\n+#endif\n \n #ifdef COMPARE_WITH_FNMATCH\n #include <fnmatch.h>\n \n-int fnmatch_errors = 0;\n+static int fnmatch_errors = 0;\n #endif\n \n-int wildmatch_errors = 0;\n-char number_separator = ',';\n+static int wildmatch_errors = 0;\n \n typedef char bool;\n \n-int output_iterations = 0;\n-int explode_mod = 0;\n-int empties_mod = 0;\n-int empty_at_start = 0;\n-int empty_at_end = 0;\n-\n-static struct poptOption long_options[] = {\n-  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */\n-  {\"iterations\",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},\n-  {\"empties\",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},\n-  {\"explode\",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},\n-  {0,0,0,0, 0, 0, 0}\n+static int explode_mod = 0;\n+static int empties_mod = 0;\n+static int empty_at_start = 0;\n+static int empty_at_end = 0;\n+static char *empties;\n+\n+static struct option options[] = {\n+  OPT_STRING('e', \"empties\", &empties, \"\", \"\"),\n+  OPT_INTEGER('x', \"explode\", &explode_mod, \"\"),\n+  OPT_END(),\n };\n \n /* match just at the start of string (anchored tests) */\n@@ -100,51 +104,33 @@ run_test(int line, bool matches,\n \tfnmatch_errors++;\n     }\n #endif\n-    if (output_iterations) {\n-\tprintf(\"%d: \\\"%s\\\" iterations = %d\\n\", line, pattern,\n-\t       wildmatch_iteration_count);\n-    }\n }\n \n int\n main(int argc, char **argv)\n {\n     char buf[2048], *s, *string[2], *end[2];\n-    const char *arg;\n     FILE *fp;\n-    int opt, line, i, flag[2];\n-    poptContext pc = poptGetContext(\"wildtest\", argc, (const char**)argv,\n-\t\t\t\t    long_options, 0);\n-\n-    while ((opt = poptGetNextOpt(pc)) != -1) {\n-\tswitch (opt) {\n-\t  case 'e':\n-\t    arg = poptGetOptArg(pc);\n-\t    empties_mod = atoi(arg);\n-\t    if (strchr(arg, 's'))\n-\t\tempty_at_start = 1;\n-\t    if (strchr(arg, 'e'))\n-\t\tempty_at_end = 1;\n-\t    if (!explode_mod)\n-\t\texplode_mod = 1024;\n-\t    break;\n-\t  default:\n-\t    fprintf(stderr, \"%s: %s\\n\",\n-\t\t    poptBadOption(pc, POPT_BADOPTION_NOALIAS),\n-\t\t    poptStrerror(opt));\n-\t    exit(1);\n-\t}\n+    int line, i, flag[2];\n+    const char *help[] = { NULL };\n+\n+    argc = parse_options(argc, (const char **)argv, \"\", options, help, 0);\n+    if (argc != 1)\n+\t    die(\"redundant options\");\n+    if (empties) {\n+\tconst char *arg = empties;\n+\tempties_mod = atoi(arg);\n+\tif (strchr(empties, 's'))\n+\t    empty_at_start = 1;\n+\tif (strchr(arg, 'e'))\n+\t    empty_at_end = 1;\n+\tif (!explode_mod)\n+\t    explode_mod = 1024;\n     }\n \n     if (explode_mod && !empties_mod)\n \tempties_mod = 1024;\n \n-    argv = (char**)poptGetArgs(pc);\n-    if (!argv || argv[1]) {\n-\tfprintf(stderr, \"Usage: wildtest [OPTIONS] TESTFILE\\n\");\n-\texit(1);\n-    }\n-\n     if ((fp = fopen(*argv, \"r\")) == NULL) {\n \tfprintf(stderr, \"Unable to open %s\\n\", *argv);\n \texit(1);\ndiff --git a/wildmatch.c b/wildmatch.c\nindex c7f7f9f..f153f8a 100644\n--- a/wildmatch.c\n+++ b/wildmatch.c\n@@ -9,7 +9,13 @@\n **  work differently than '*', and to fix the character-class code.\n */\n \n-#include \"rsync.h\"\n+#include <stddef.h>\n+#include <ctype.h>\n+#include <string.h>\n+\n+#include \"wildmatch.h\"\n+\n+typedef unsigned char uchar;\n \n /* What character marks an inverted character class? */\n #define NEGATE_CLASS\t'!'\n@@ -53,10 +59,6 @@\n #define ISUPPER(c) (ISASCII(c) && isupper(c))\n #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))\n \n-#ifdef WILD_TEST_ITERATIONS\n-int wildmatch_iteration_count;\n-#endif\n-\n /* Match pattern \"p\" against the a virtually-joined string consisting\n  * of \"text\" and any strings in array \"a\". */\n static int dowild(const uchar *p, const uchar *text,\n@@ -64,10 +66,6 @@ static int dowild(const uchar *p, const uchar *text,\n {\n     uchar p_ch;\n \n-#ifdef WILD_TEST_ITERATIONS\n-    wildmatch_iteration_count++;\n-#endif\n-\n     for ( ; (p_ch = *p) != '\\0'; text++, p++) {\n \tint matched, special;\n \tuchar t_ch, prev_ch;\n@@ -289,9 +287,6 @@ static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)\n int wildmatch(const char *pattern, const char *text)\n {\n     static const uchar *nomore[1]; /* A NULL pointer. */\n-#ifdef WILD_TEST_ITERATIONS\n-    wildmatch_iteration_count = 0;\n-#endif\n     return dowild((const uchar*)pattern, (const uchar*)text, nomore, 0) == TRUE;\n }\n \n@@ -300,9 +295,6 @@ int iwildmatch(const char *pattern, const char *text)\n {\n     static const uchar *nomore[1]; /* A NULL pointer. */\n     int ret;\n-#ifdef WILD_TEST_ITERATIONS\n-    wildmatch_iteration_count = 0;\n-#endif\n     ret = dowild((const uchar*)pattern, (const uchar*)text, nomore, 1) == TRUE;\n     return ret;\n }\n@@ -319,10 +311,6 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)\n     const uchar *text;\n     int matched;\n \n-#ifdef WILD_TEST_ITERATIONS\n-    wildmatch_iteration_count = 0;\n-#endif\n-\n     if (where > 0)\n \ttext = trailing_N_elements(&a, where);\n     else\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200610","messageId":"1349412069-627-10-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 09/10] Support \"**\" in .gitignore and .gitattributes patterns using wildmatch()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:08Z","receivedAt":"2012-10-05T04:41:08Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/gitignore.txt        |  3 +++\n attr.c                             |  4 +++-\n dir.c                              |  5 ++++-\n t/t0003-attributes.sh              | 17 +++++++++++++++++\n t/t3001-ls-files-others-exclude.sh | 11 +++++++++++\n 5 files changed, 38 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt\nindex c1f692a..eb81d31 100644\n--- a/Documentation/gitignore.txt\n+++ b/Documentation/gitignore.txt\n@@ -93,6 +93,9 @@ PATTERN FORMAT\n    For example, \"Documentation/{asterisk}.html\" matches\n    \"Documentation/git.html\" but not \"Documentation/ppc/ppc.html\"\n    or \"tools/perf/Documentation/perf.html\".\n++\n+Contrary to fnmatch(3), git matches \"**\" to anything including\n+slashes, similar to rsync(1).\n \n  - A leading slash matches the beginning of the pathname.\n    For example, \"/{asterisk}.c\" matches \"cat-file.c\" but not\ndiff --git a/attr.c b/attr.c\nindex 1aa058e..3103c66 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -12,6 +12,7 @@\n #include \"exec_cmd.h\"\n #include \"attr.h\"\n #include \"dir.h\"\n+#include \"wildmatch.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -709,7 +710,8 @@ static int path_matches(const char *pathname, int pathlen,\n \tif (!namelen || prefix > namelen)\n \t\treturn 0;\n \n-\treturn fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;\n+\treturn (ignore_case && iwildmatch(pattern, name)) ||\n+\t\t(!ignore_case && wildmatch(pattern, name));\n }\n \n static int macroexpand_one(int attr_nr, int rem);\ndiff --git a/dir.c b/dir.c\nindex c6a0275..cb78273 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"refs.h\"\n+#include \"wildmatch.h\"\n \n struct path_simplify {\n \tint len;\n@@ -600,7 +601,9 @@ 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 (!namelen ||\n+\t\t    ((ignore_case && iwildmatch(exclude, name)) ||\n+\t\t     (!ignore_case && wildmatch(exclude, name))))\n \t\t\treturn to_exclude;\n \t}\n \treturn -1; /* undecided */\ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex febc45c..6c3c554 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -232,4 +232,21 @@ test_expect_success 'bare repository: test info/attributes' '\n \tattr_check subdir/a/i unspecified\n '\n \n+test_expect_success '\"**\" test' '\n+\tcd .. &&\n+\techo \"**/f foo=bar\" >.gitattributes &&\n+\tcat <<\\EOF >expect &&\n+f: foo: unspecified\n+a/f: foo: bar\n+a/b/f: foo: bar\n+a/b/c/f: foo: bar\n+EOF\n+\tgit check-attr foo -- \"f\" >actual 2>err &&\n+\tgit check-attr foo -- \"a/f\" >>actual 2>>err &&\n+\tgit check-attr foo -- \"a/b/f\" >>actual 2>>err &&\n+\tgit check-attr foo -- \"a/b/c/f\" >>actual 2>>err &&\n+\ttest_cmp expect actual &&\n+\ttest_line_count = 0 err\n+'\n+\n test_done\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex c8fe978..67c8bcf 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -214,4 +214,15 @@ test_expect_success 'subdirectory ignore (l1)' '\n \ttest_cmp expect actual\n '\n \n+\n+test_expect_success 'ls-files with \"**\" patterns' '\n+\tcat <<\\EOF >expect &&\n+one/a.1\n+one/two/a.1\n+three/a.1\n+EOF\n+\tgit ls-files -o -i --exclude \"**/a.1\" >actual\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200603","messageId":"1349412069-627-11-git-send-email-pclouds@gmail.com","threadId":"31733","inReplyTo":"1349412069-627-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 10/10] gitignore: forbid \"abc**def\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T04:41:09Z","receivedAt":"2012-10-05T04:41:09Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Deep down wildmatch() sees \"**\" as \"*\" that can also match slashes. On\nthe surface, it may be confusing to users as the above pattern can\nmatch \"abcdef\", \"abcxyzdef\", \"abc/def\", \"abc/x/def\",\n\"abc/x/y/def\"... For now we just forbid it. Users can only do\n\"**/def\", \"abc/**\" or \"abc/**/def\". The syntax may be re-enabled in\nfuture.\n\nThere's a minor problem with this particular approach. \"**\" inside\nsquare brackets are mistaken as the wildcard while they are not. Git\nshows a confusing message when users do that.\n\nNote that this patch hides a potential problem that if \"abc**def\" is\never supported, EXC_FLAG_NODIR flag should be turned off or only the\nbase name is matched against the pattern, which makes no sense.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/gitignore.txt        |  6 ++++--\n dir.c                              | 12 +++++++++---\n t/t0003-attributes.sh              |  5 +++++\n t/t3001-ls-files-others-exclude.sh |  5 +++++\n 4 files changed, 23 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt\nindex eb81d31..ad9fc2f 100644\n--- a/Documentation/gitignore.txt\n+++ b/Documentation/gitignore.txt\n@@ -94,8 +94,10 @@ PATTERN FORMAT\n    \"Documentation/git.html\" but not \"Documentation/ppc/ppc.html\"\n    or \"tools/perf/Documentation/perf.html\".\n +\n-Contrary to fnmatch(3), git matches \"**\" to anything including\n-slashes, similar to rsync(1).\n+In addition to fnmatch(3) syntax, \"**\" can be used to match one or\n+more directories. For example, \"abc/**/def\" matches \"abc/x/def\",\n+\"abc/x/y/def\", \"abc/x/y/z/def\" and so on. \"**\" must be wrapped by\n+slashes.\n \n  - A leading slash matches the beginning of the pathname.\n    For example, \"/{asterisk}.c\" matches \"cat-file.c\" but not\ndiff --git a/dir.c b/dir.c\nindex cb78273..f30117f 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -327,12 +327,18 @@ void parse_exclude_pattern(const char **pattern,\n \t\tlen--;\n \t\t*flags |= EXC_FLAG_MUSTBEDIR;\n \t}\n+\t*flags |= EXC_FLAG_NODIR;\n \tfor (i = 0; i < len; i++) {\n \t\tif (p[i] == '/')\n-\t\t\tbreak;\n+\t\t\t*flags &= ~EXC_FLAG_NODIR;\n+\t\tif ((p[i] == '*' && p[i + 1] == '*' &&\n+\t\t     (i == 0 || p[i - 1] != '\\\\')) &&\n+\t\t    !((i == 0 || p[i - 1] == '/') &&\n+\t\t      (p[i + 2] == '\\0' ||\n+\t\t       p[i + 2] == '/' ||\n+\t\t       (p[i + 2] == '\\\\' && p[i + 3] == '/'))))\n+\t\t\tdie(_(\"** in .gitignore or .gitattributes must be wrapped by slashes\"));\n \t}\n-\tif (i == len)\n-\t\t*flags |= EXC_FLAG_NODIR;\n \t*nowildcardlen = simple_length(p);\n \t/*\n \t * we should have excluded the trailing slash from 'p' too,\ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex 6c3c554..ddeb321 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -249,4 +249,9 @@ EOF\n \ttest_line_count = 0 err\n '\n \n+test_expect_success '\"**\" with no slashes test' '\n+\techo \"a**f foo=bar\" >.gitattributes &&\n+\ttest_must_fail git check-attr foo -- \"f\"\n+'\n+\n test_done\ndiff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh\nindex 67c8bcf..f5c62d0 100755\n--- a/t/t3001-ls-files-others-exclude.sh\n+++ b/t/t3001-ls-files-others-exclude.sh\n@@ -225,4 +225,9 @@ EOF\n \ttest_cmp expect actual\n '\n \n+\n+test_expect_success 'ls-files with \"**\" patterns and no slashes' '\n+\ttest_must_fail git ls-files -o -i --exclude \"one**a.1\"\n+'\n+\n test_done\n-- \n1.7.12.1.405.gb727dc9\n"},{"id":"200613","messageId":"alpine.DEB.2.00.1210051129510.16797@ds9.cixit.se","threadId":"31733","inReplyTo":"1349412069-627-6-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 05/10] Import wildmatch from rsync","fromName":"Peter Krefting","fromEmail":"peter@softwolves.pp.se","sentAt":"2012-10-05T10:30:54Z","receivedAt":"2012-10-05T10:30:54Z","isPatch":true,"sender":{"key":"peter@softwolves.pp.se","avatar":"https://avatars.githubusercontent.com/u/990764?v=4"},"body":"> These files are from rsync.git commit\n> f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit\n> before rsync turned GPL-3.\n\nHowever:\n\n> diff --git a/test-wildmatch.c b/test-wildmatch.c\n[...]\n> + * This program is free software; you can redistribute it and/or modify\n> + * it under the terms of the GNU General Public License as published by\n> + * the Free Software Foundation; either version 3 of the License, or\n> + * (at your option) any later version.\n\n-- \n\\\\// Peter - http://www.softwolves.pp.se/\n"},{"id":"200618","messageId":"CACsJy8A_+-2tp43vtGHdXnR06x1TDoa9QDsMGPOmT2x0-itVvA@mail.gmail.com","threadId":"31733","inReplyTo":"alpine.DEB.2.00.1210051129510.16797@ds9.cixit.se","subject":"Re: [PATCH 05/10] Import wildmatch from rsync","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-05T11:18:46Z","receivedAt":"2012-10-05T11:18:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Oct 5, 2012 at 5:30 PM, Peter Krefting <peter@softwolves.pp.se> wrote:\n>> These files are from rsync.git commit\n>> f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit\n>> before rsync turned GPL-3.\n>\n>\n> However:\n>\n>> diff --git a/test-wildmatch.c b/test-wildmatch.c\n>\n> [...]\n>\n>> + * This program is free software; you can redistribute it and/or modify\n>> + * it under the terms of the GNU General Public License as published by\n>> + * the Free Software Foundation; either version 3 of the License, or\n>> + * (at your option) any later version.\n\nA copy mistake. I probably did not look closely at this file as it's\nnot the main source, we could even rewrite it with little effort. Will\nupdate with GPL2 version next round.\n-- \nDuy\n"},{"id":"200633","messageId":"7vy5jku549.fsf@alter.siamese.dyndns.org","threadId":"31733","inReplyTo":"1349412069-627-5-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-10-05T18:48:54Z","receivedAt":"2012-10-05T18:48:54Z","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> +Unlike `.gitignore`, negative patterns are not supported.\n> +Patterns that match directories are also not supported.\n\nIs \"are not supported\" the right phrasing?\n\nI think it makes perfect sense not to forbid \"!path attr1\", because\nit is unclear what it means (e.g. \"path -attr1\" vs \"path !attr1\").\nSo I would say \"Negative patterns are forbidden as they do not make\nany sense\".\n\nBut for the latter, I think it makes a lot more sense to just accept\n\"path/ attr1\" and doing nothing.  The user requests to set an\nattribute to \"path\" that has to be a directory, and there is nothing\nwrong in such a request in itself.  But nothing in git asks for\nattributes for directories (because we do not track directories),\nand such a request happens to be a no-op.\n"},{"id":"200641","messageId":"CACnwZYeob34c6hbMX-CNvJ67Qu5b+v7J8SbfcBOgZv6Qu+aqFg@mail.gmail.com","threadId":"31733","inReplyTo":"1349412069-627-9-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 08/10] Integrate wildmatch to git","fromName":"Thiago Farina","fromEmail":"tfransosi@gmail.com","sentAt":"2012-10-05T21:20:02Z","receivedAt":"2012-10-05T21:20:02Z","isPatch":true,"sender":{"key":"tfransosi@gmail.com","avatar":"https://avatars.githubusercontent.com/u/970071?v=4"},"body":"On Fri, Oct 5, 2012 at 1:41 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> This makes wildmatch.c part of libgit.a and builds test-wildmatch; the\n> dependency on libpopt in the original has been replaced with the use\n> of our parse-options. Global variables in test-wildmatch are marked\n> static to avoid sparse warnings.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  .gitignore           |  1 +\n>  Makefile             |  3 ++\n>  t/t3070-wildmatch.sh | 27 ++++++++++++++++\n>  test-wildmatch.c     | 88 ++++++++++++++++++++++------------------------------\n>  wildmatch.c          | 26 +++++-----------\n>  5 files changed, 75 insertions(+), 70 deletions(-)\n>  create mode 100755 t/t3070-wildmatch.sh\n>\n> diff --git a/test-wildmatch.c b/test-wildmatch.c\n> index 88585c2..bb726c8 100644\n> --- a/test-wildmatch.c\n> +++ b/test-wildmatch.c\n> @@ -19,34 +19,38 @@\n>\n>  /*#define COMPARE_WITH_FNMATCH*/\n>\n> -#define WILD_TEST_ITERATIONS\n> -#include \"lib/wildmatch.c\"\n> +#include \"cache.h\"\n> +#include \"parse-options.h\"\n> +#include \"wildmatch.h\"\n>\n> -#include <popt.h>\n> +#ifndef MAXPATHLEN\n> +#define MAXPATHLEN 1024\n> +#endif\n> +#ifdef NO_STRLCPY\n> +#include \"compat/strlcpy.c\"\n> +#define strlcpy gitstrlcpy\n> +#endif\n>\n>  #ifdef COMPARE_WITH_FNMATCH\n>  #include <fnmatch.h>\n>\n> -int fnmatch_errors = 0;\n> +static int fnmatch_errors = 0;\n>  #endif\n>\n> -int wildmatch_errors = 0;\n> -char number_separator = ',';\n> +static int wildmatch_errors = 0;\n>\n>  typedef char bool;\n>\n> -int output_iterations = 0;\n> -int explode_mod = 0;\n> -int empties_mod = 0;\n> -int empty_at_start = 0;\n> -int empty_at_end = 0;\n> -\n> -static struct poptOption long_options[] = {\n> -  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */\n> -  {\"iterations\",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},\n> -  {\"empties\",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},\n> -  {\"explode\",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},\n> -  {0,0,0,0, 0, 0, 0}\n> +static int explode_mod = 0;\nIsn't static variables like this initialized to zero by default? There\nis a high chance that I might be wrong though.\n"},{"id":"200647","messageId":"CACsJy8BWJg0sr-6iG4LwJjkSM46=CBgddDac4dDR2o3HZ8_25g@mail.gmail.com","threadId":"31733","inReplyTo":"7vy5jku549.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-06T05:02:17Z","receivedAt":"2012-10-06T05:02:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n>\n>> +Unlike `.gitignore`, negative patterns are not supported.\n>> +Patterns that match directories are also not supported.\n>\n> Is \"are not supported\" the right phrasing?\n>\n> I think it makes perfect sense not to forbid \"!path attr1\", because\n> it is unclear what it means (e.g. \"path -attr1\" vs \"path !attr1\").\n> So I would say \"Negative patterns are forbidden as they do not make\n> any sense\".\n\nOK\n\n> But for the latter, I think it makes a lot more sense to just accept\n> \"path/ attr1\" and doing nothing.  The user requests to set an\n> attribute to \"path\" that has to be a directory, and there is nothing\n> wrong in such a request in itself.  But nothing in git asks for\n> attributes for directories (because we do not track directories),\n> and such a request happens to be a no-op.\n\nOr the user might think \"path/ attr1\" sets attr1 for all files under\n\"path/\" because it does not make sense to attach attributes to a\ndirectory in git.\n-- \nDuy\n"},{"id":"200649","messageId":"7vk3v4rwkv.fsf@alter.siamese.dyndns.org","threadId":"31733","inReplyTo":"CACsJy8BWJg0sr-6iG4LwJjkSM46=CBgddDac4dDR2o3HZ8_25g@mail.gmail.com","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-10-06T05:36:16Z","receivedAt":"2012-10-06T05:36:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:\n\n> On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n>>\n>>> +Unlike `.gitignore`, negative patterns are not supported.\n>>> +Patterns that match directories are also not supported.\n>>\n>> Is \"are not supported\" the right phrasing?\n>>\n>> I think it makes perfect sense not to forbid \"!path attr1\", because\n>> it is unclear what it means (e.g. \"path -attr1\" vs \"path !attr1\").\n>> So I would say \"Negative patterns are forbidden as they do not make\n>> any sense\".\n>\n> OK\n>\n>> But for the latter, I think it makes a lot more sense to just accept\n>> \"path/ attr1\" and doing nothing.  The user requests to set an\n>> attribute to \"path\" that has to be a directory, and there is nothing\n>> wrong in such a request in itself.  But nothing in git asks for\n>> attributes for directories (because we do not track directories),\n>> and such a request happens to be a no-op.\n>\n> Or the user might think \"path/ attr1\" sets attr1 for all files under\n> \"path/\" because it does not make sense to attach attributes to a\n> directory in git.\n\nHrm, isn't that reasoning flawed at least for two reasons?\n\n - One reasonable way to conceptually unify excludes and attributes\n   is to consider \"being ignored\" as just one kind of attribute. If\n   you imagine an ideal world where we did not have any .gitignore,\n   an entry \"path/\" in .gitignore would be \"path/ excluded\", and an\n   entry \"!path/\" in .gitignore would be \"path/ -excluded\".  Realize\n   that on the exclude side, we are already assigning an \"attribute\"\n   to a directory.\n\n - Setting attr1 to everything in path is spelled \"path1/**/* attr1\"\n   if we are to adopt \"/**/ is zero or more intermediate levels\"\n   enhancement.\n\n   We may not have a need to assign a real attribute to a directory\n   right now, because nothing in Git asks for an attribute for a\n   directory. But that does not necessarily mean we would never need a\n   way to give an attribute to a directory but not to its contents.\n\nIf one does not think it through, the \"path/ excluded\" example might\nappear that there is no difference between setting exclude to the\npath itself and setting it to path and everything underneath it, but\nthat comes largely from the nature of \"exclude\" attribute (think of\n\"exclude\" attribute as \"exclude itself and everything under it).\n\nThere is no reason to assume other attributes we may want to give to\na directory share the same \"recursive\" kind of semantics.\n"},{"id":"200652","messageId":"CACsJy8Cn0rAV+u+8tzD2HOm1XkfKBPkFJVrPQAojMBtYe6wxRg@mail.gmail.com","threadId":"31733","inReplyTo":"7vk3v4rwkv.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-06T06:43:47Z","receivedAt":"2012-10-06T06:43:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Or the user might think \"path/ attr1\" sets attr1 for all files under\n>> \"path/\" because it does not make sense to attach attributes to a\n>> directory in git.\n>\n>    ...\n>\n>    We may not have a need to assign a real attribute to a directory\n>    right now, because nothing in Git asks for an attribute for a\n>    directory. But that does not necessarily mean we would never need a\n>    way to give an attribute to a directory but not to its contents.\n\nExactly why we should not make \"path/ attr\" no-op. If we want to make\nit meaningful some day in future, I don't think we want those no-ops\nlay around and suddenly cause changes in behavior with a new version\nof git.\n\n> If one does not think it through, the \"path/ excluded\" example might\n> appear that there is no difference between setting exclude to the\n> path itself and setting it to path and everything underneath it, but\n> that comes largely from the nature of \"exclude\" attribute (think of\n> \"exclude\" attribute as \"exclude itself and everything under it).\n\n>From a user perspective, the thinking through portion is usually less\nthan the try-and-see.\n\n> There is no reason to assume other attributes we may want to give to\n> a directory share the same \"recursive\" kind of semantics.\n-- \nDuy\n"},{"id":"200653","messageId":"7v4nm8rspn.fsf@alter.siamese.dyndns.org","threadId":"31733","inReplyTo":"CACsJy8Cn0rAV+u+8tzD2HOm1XkfKBPkFJVrPQAojMBtYe6wxRg@mail.gmail.com","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-10-06T06:59:48Z","receivedAt":"2012-10-06T06:59:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:\n\n> On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> Or the user might think \"path/ attr1\" sets attr1 for all files under\n>>> \"path/\" because it does not make sense to attach attributes to a\n>>> directory in git.\n>>\n>>    ...\n>>\n>>    We may not have a need to assign a real attribute to a directory\n>>    right now, because nothing in Git asks for an attribute for a\n>>    directory. But that does not necessarily mean we would never need a\n>>    way to give an attribute to a directory but not to its contents.\n>\n> Exactly why we should not make \"path/ attr\" no-op. If we want to make\n> it meaningful some day in future, I don't think we want those no-ops\n> lay around and suddenly cause changes in behavior with a new version\n> of git.\n\nI do not think you understood.  \"path/ attr\" is a no-op from the\npoint of view of the *users* of the current versions of Git.  It is\nperfectly fine to accept and apply attr to \"path/\"; no codepath in\nGit should be asking about path/ anyway, so it ends up to be a\nno-op.  You shouldn't be erroring out at the syntactic level, i.e.\nwhen these lines are parsed.\n"},{"id":"200659","messageId":"k4otes$jll$1@ger.gmane.org","threadId":"31733","inReplyTo":"CACnwZYeob34c6hbMX-CNvJ67Qu5b+v7J8SbfcBOgZv6Qu+aqFg@mail.gmail.com","subject":"Re: [PATCH 08/10] Integrate wildmatch to git","fromName":"Joachim Schmitz","fromEmail":"jojo@schmitz-digital.de","sentAt":"2012-10-06T09:25:35Z","receivedAt":"2012-10-06T09:25:35Z","isPatch":true,"sender":{"key":"jojo@schmitz-digital.de","avatar":"https://avatars.githubusercontent.com/u/1786669?v=4"},"body":"Thiago Farina wrote:\n> On Fri, Oct 5, 2012 at 1:41 AM, Nguyễn Thái Ngọc Duy\n> <pclouds@gmail.com> wrote:\n>> This makes wildmatch.c part of libgit.a and builds test-wildmatch;\n>> the dependency on libpopt in the original has been replaced with the\n>> use\n>> of our parse-options. Global variables in test-wildmatch are marked\n>> static to avoid sparse warnings.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> ---\n>>  .gitignore           |  1 +\n>>  Makefile             |  3 ++\n>>  t/t3070-wildmatch.sh | 27 ++++++++++++++++\n>>  test-wildmatch.c     | 88\n>>  ++++++++++++++++++++++------------------------------ wildmatch.c\n>>  | 26 +++++----------- 5 files changed, 75 insertions(+), 70\n>>  deletions(-) create mode 100755 t/t3070-wildmatch.sh\n>>\n>> diff --git a/test-wildmatch.c b/test-wildmatch.c\n>> index 88585c2..bb726c8 100644\n>> --- a/test-wildmatch.c\n>> +++ b/test-wildmatch.c\n>> @@ -19,34 +19,38 @@\n>>\n>>  /*#define COMPARE_WITH_FNMATCH*/\n>>\n>> -#define WILD_TEST_ITERATIONS\n>> -#include \"lib/wildmatch.c\"\n>> +#include \"cache.h\"\n>> +#include \"parse-options.h\"\n>> +#include \"wildmatch.h\"\n>>\n>> -#include <popt.h>\n>> +#ifndef MAXPATHLEN\n>> +#define MAXPATHLEN 1024\n>> +#endif\n>> +#ifdef NO_STRLCPY\n>> +#include \"compat/strlcpy.c\"\n>> +#define strlcpy gitstrlcpy\n>> +#endif\n>>\n>>  #ifdef COMPARE_WITH_FNMATCH\n>>  #include <fnmatch.h>\n>>\n>> -int fnmatch_errors = 0;\n>> +static int fnmatch_errors = 0;\n>>  #endif\n>>\n>> -int wildmatch_errors = 0;\n>> -char number_separator = ',';\n>> +static int wildmatch_errors = 0;\n>>\n>>  typedef char bool;\n>>\n>> -int output_iterations = 0;\n>> -int explode_mod = 0;\n>> -int empties_mod = 0;\n>> -int empty_at_start = 0;\n>> -int empty_at_end = 0;\n>> -\n>> -static struct poptOption long_options[] = {\n>> -  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc\n>> */\n>> -  {\"iterations\",     'i', POPT_ARG_NONE,   &output_iterations, 0,\n>> 0, 0},\n>> -  {\"empties\",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},\n>> -  {\"explode\",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},\n>> -  {0,0,0,0, 0, 0, 0}\n>> +static int explode_mod = 0;\n> Isn't static variables like this initialized to zero by default? There\n> is a high chance that I might be wrong though.\n\nC99,\n5.1.2.1: All objects with static storage duration shall be initialized (set \nto their initial values) before program startup.\n6.2.4.2: An object whose identifier is declared with external or internal \nlinkage, or with the storage-class specifier static has static storage \nduration. Its lifetime is the entire execution of the program and its stored \nvalue is initialized only once, prior to program startup.\n6.7.8.10: If an object that has automatic storage duration is not \ninitialized explicitly, its value is indeterminate. If an object that has \nstatic storage duration is not initialized explicitly, then:\n— if it has pointer type, it is initialized to a null pointer;\n— if it has arithmetic type, it is initialized to (positive or unsigned) \nzero;\n— if it is an aggregate, every member is initialized (recursively) according \nto these rules;\n— if it is a union, the first named member is initialized (recursively) \naccording to these rules.\n\nSo seems you're right ;-) \n"},{"id":"200738","messageId":"CACsJy8DhymVp1ncHvtqd4S7TkBDQvNKBf3A0JTft9bX8_-WX_w@mail.gmail.com","threadId":"31733","inReplyTo":"7vy5jku549.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-10-08T03:26:35Z","receivedAt":"2012-10-08T03:26:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n>\n>> +Unlike `.gitignore`, negative patterns are not supported.\n>> +Patterns that match directories are also not supported.\n>\n> Is \"are not supported\" the right phrasing?\n>\n> I think it makes perfect sense not to forbid \"!path attr1\", because\n> it is unclear what it means (e.g. \"path -attr1\" vs \"path !attr1\").\n> So I would say \"Negative patterns are forbidden as they do not make\n> any sense\".\n\n!path/sub/ alone does not mean anything. It must be used together with\na positive pattern to define the set of paths the same attribute\nassignment statement applies to. This makes sense (attr, -attr1 or\n!attr1 are all OK):\n\n*.c attr1\n!foo.c attr1\n\nBut this does not (actually \"!foo.c\" line has no effects because of\ndifferent attribute assignments):\n\n*.c attr1\n!foo.c attr2\n\nIt could be even more confusing in multiple attribute manipulation:\n\n*.c attr1\n*.h -attr2\n!foo.[ch] attr1 -attr2\n\nSo \"not supported\" and \"forbidden\" are equally OK. I just want to\nraise a point that it has some use before we go for \"forbidden\".\n\n> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:\n>\n>> On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>>>> Or the user might think \"path/ attr1\" sets attr1 for all files under\n>>>> \"path/\" because it does not make sense to attach attributes to a\n>>>> directory in git.\n>>>\n>>>    ...\n>>>\n>>>    We may not have a need to assign a real attribute to a directory\n>>>    right now, because nothing in Git asks for an attribute for a\n>>>    directory. But that does not necessarily mean we would never need a\n>>>    way to give an attribute to a directory but not to its contents.\n>>\n>> Exactly why we should not make \"path/ attr\" no-op. If we want to make\n>> it meaningful some day in future, I don't think we want those no-ops\n>> lay around and suddenly cause changes in behavior with a new version\n>> of git.\n>\n> I do not think you understood.  \"path/ attr\" is a no-op from the\n> point of view of the *users* of the current versions of Git.  It is\n> perfectly fine to accept and apply attr to \"path/\"; no codepath in\n> Git should be asking about path/ anyway, so it ends up to be a\n> no-op.  You shouldn't be erroring out at the syntactic level, i.e.\n> when these lines are parsed.\n\nMy objection is no-op lines are timed bombs. But users can already do\n\"dir attr\" (no slashes), which is no-op. So yeah, no-op is fine.\n-- \nDuy\n"},{"id":"200750","messageId":"7v391pnetw.fsf@alter.siamese.dyndns.org","threadId":"31733","inReplyTo":"CACsJy8DhymVp1ncHvtqd4S7TkBDQvNKBf3A0JTft9bX8_-WX_w@mail.gmail.com","subject":"Re: [PATCH 04/10] attr: more matching optimizations from .gitignore","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-10-08T15:50:03Z","receivedAt":"2012-10-08T15:50:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:\n\n> My objection is no-op lines are timed bombs. But users can already do\n> \"dir attr\" (no slashes), which is no-op. So yeah, no-op is fine.\n\nExactly. If you are not catching and barfing the no-slashed variant\nat the syntax level (and you shouldn't), you shouldn't do so for\nslashed ones.\n"}]}