{"thread":{"id":"30458","subject":"[PATCH/RFC 0/3] Support ignore rule \"**\" using wildmatch from rsync","startedAt":"2012-05-07T13:01:54Z","lastAt":"2012-05-07T19:05:11Z","messageCount":5,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"190992","messageId":"1336395717-7799-1-git-send-email-pclouds@gmail.com","threadId":"30458","inReplyTo":null,"subject":"[PATCH/RFC 0/3] Support ignore rule \"**\" using wildmatch from rsync","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-05-07T13:01:54Z","receivedAt":"2012-05-07T13:01:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"I've been looking for something like this. Bash 4 has a similar\nfeature, but it's GPL-3. rsync is also GPL-3. Luckily this feature was\navailble before the license change.\n\nI'm not sure if we should import wildmatch just to support \"**\". It seems\na nice and often requested feature. And the code looks more readable than\ncompat/fnmatch, which is good since I don't think we can cherry pick\nfrom (gpl3) upstream.\n\nWe would need to add case folding support or convert \"abc\" to\n\"[aA][bB][cC]\". Performance vs fnmatch is another question but we\ncould switch to wildmatch only in the presence of \"**\" if wildmatch\nperf sucks. Even pathspec may make use of this, thanks to pathspec\nmagic.\n\nI have not looked carefully at it and this series is more like a proof\nof concept than a candidate for 'pu'. Any comments in favor or oppose\nthis?\n\nNguyễn Thái Ngọc Duy (3):\n  Import wildmatch from rsync\n  Integrate wildmatch to git\n  gitignore: support \"**\" with wildmatch()\n\n Makefile                       |    3 +\n compat/wildmatch.c             |  356 ++++++++++++++++++++++++++++++++++++++++\n compat/wildmatch.h             |    6 +\n dir.c                          |   20 ++-\n dir.h                          |    1 +\n t/t3070-wildmatch.sh           |   27 +++\n t/t3070-wildmatch/wildtest.txt |  165 +++++++++++++++++++\n test-wildmatch.c               |  162 ++++++++++++++++++\n 8 files changed, 736 insertions(+), 4 deletions(-)\n create mode 100644 compat/wildmatch.c\n create mode 100644 compat/wildmatch.h\n create mode 100755 t/t3070-wildmatch.sh\n create mode 100644 t/t3070-wildmatch/wildtest.txt\n create mode 100644 test-wildmatch.c\n\n-- \n1.7.8.36.g69ee2\n"},{"id":"190993","messageId":"1336395717-7799-2-git-send-email-pclouds@gmail.com","threadId":"30458","inReplyTo":"1336395717-7799-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/RFC 1/3] Import wildmatch from rsync","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-05-07T13:01:55Z","receivedAt":"2012-05-07T13:01:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"wildmatch is the machinery behind rsync's include/exclude pattern\nrules. In general it is similar to git's ignore rules, except that\n\"**\" matches anything including patterns\n\nThese files are imported unmodified from rsync.git commit\nf92f5b166e3019db42bc7fe1aa2f1a9178cd215d when they were still in GPL-2.\n\nrsync.git           ->  git.git\nlib/wildmatch.[ch]      compat/wildmatch.[ch]\nwildtest.c              test-wildmatch.c\nwildtest.txt            t/t3070-wildmatch/wildtest.txt\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n compat/wildmatch.c             |  368 ++++++++++++++++++++++++++++++++++++++++\n compat/wildmatch.h             |    6 +\n t/t3070-wildmatch/wildtest.txt |  165 ++++++++++++++++++\n test-wildmatch.c               |  216 +++++++++++++++++++++++\n 4 files changed, 755 insertions(+), 0 deletions(-)\n create mode 100644 compat/wildmatch.c\n create mode 100644 compat/wildmatch.h\n create mode 100644 t/t3070-wildmatch/wildtest.txt\n create mode 100644 test-wildmatch.c\n\ndiff --git a/compat/wildmatch.c b/compat/wildmatch.c\nnew file mode 100644\nindex 0000000..f3a1731\n--- /dev/null\n+++ b/compat/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/compat/wildmatch.h b/compat/wildmatch.h\nnew file mode 100644\nindex 0000000..e7f1a35\n--- /dev/null\n+++ b/compat/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);\ndiff --git a/t/t3070-wildmatch/wildtest.txt b/t/t3070-wildmatch/wildtest.txt\nnew file mode 100644\nindex 0000000..42c1678\n--- /dev/null\n+++ b/t/t3070-wildmatch/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..3456bac\n--- /dev/null\n+++ b/test-wildmatch.c\n@@ -0,0 +1,216 @@\n+/*\n+ * Test suite for the wildmatch code.\n+ *\n+ * Copyright (C) 2003-2007 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 version 2 as\n+ * published by the Free Software Foundation.\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, write to the Free Software Foundation, Inc.,\n+ * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.\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+\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, bool same_as_fnmatch,\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+#else\n+    same_as_fnmatch = 0; /* Get rid of unused-variable compiler warning. */\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], flag[1], 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+}\n-- \n1.7.8.36.g69ee2\n"},{"id":"190994","messageId":"1336395717-7799-3-git-send-email-pclouds@gmail.com","threadId":"30458","inReplyTo":"1336395717-7799-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/RFC 2/3] Integrate wildmatch to git","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-05-07T13:01:56Z","receivedAt":"2012-05-07T13:01:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":" - Make wildmatch.c part of libgit.a\n - Rewrite test-wildmatch.c to follow git coding style (i.e. no\n   catching up with mainstream since it has turned GPL-3 anyway)\n - Put wildtest.txt into a real test case\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Makefile             |    3 +\n compat/wildmatch.c   |   26 +---\n t/t3070-wildmatch.sh |   27 +++++\n test-wildmatch.c     |  314 +++++++++++++++++++++-----------------------------\n 4 files changed, 167 insertions(+), 203 deletions(-)\n create mode 100755 t/t3070-wildmatch.sh\n\ndiff --git a/Makefile b/Makefile\nindex 2fa7211..b82f8b6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -497,6 +497,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@@ -598,6 +599,7 @@ LIB_H += compat/cygwin.h\n LIB_H += compat/mingw.h\n LIB_H += compat/obstack.h\n LIB_H += compat/terminal.h\n+LIB_H += compat/wildmatch.h\n LIB_H += compat/win32/dirent.h\n LIB_H += compat/win32/poll.h\n LIB_H += compat/win32/pthread.h\n@@ -693,6 +695,7 @@ LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n+LIB_OBJS += compat/wildmatch.o\n LIB_OBJS += config.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\ndiff --git a/compat/wildmatch.c b/compat/wildmatch.c\nindex f3a1731..791b9ef 100644\n--- a/compat/wildmatch.c\n+++ b/compat/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 static int force_lower_case = 0;\n \n /* Match pattern \"p\" against the a virtually-joined string consisting\n@@ -65,10 +67,6 @@ 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@@ -288,9 +286,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) == TRUE;\n }\n \n@@ -299,9 +294,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     force_lower_case = 1;\n     ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;\n     force_lower_case = 0;\n@@ -320,10 +312,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\ndiff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh\nnew file mode 100755\nindex 0000000..e748fc7\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-wildmatch/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 -E1 -se\n+test_wildmatch -x2\n+test_wildmatch -x2 -se\n+test_wildmatch -x3\n+test_wildmatch -x3 -E1\n+test_wildmatch -x4\n+test_wildmatch -x4 -E2 -e\n+test_wildmatch -x5\n+test_wildmatch -x5 -s\n+\n+test_done\ndiff --git a/test-wildmatch.c b/test-wildmatch.c\nindex 3456bac..b832dee 100644\n--- a/test-wildmatch.c\n+++ b/test-wildmatch.c\n@@ -17,200 +17,146 @@\n  * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.\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-\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+#include \"cache.h\"\n+#include \"parse-options.h\"\n+#include \"compat/wildmatch.h\"\n+\n+static int compare_fnmatch;\n+static int fnmatch_errors;\n+static int wildmatch_errors;\n+static int explode_mod;\n+static int empties_mod;\n+static int empty_at_start;\n+static int empty_at_end;\n \n /* match just at the start of string (anchored tests) */\n-static void\n-run_test(int line, bool matches, bool same_as_fnmatch,\n-\t const char *text, const char *pattern)\n+static void run_test(int line, int matches, int same_as_fnmatch,\n+\t\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-#else\n-    same_as_fnmatch = 0; /* Get rid of unused-variable compiler warning. */\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+\tint matched;\n+\tint fn_matched;\n+\tint flags = compare_fnmatch && strstr(pattern, \"**\") ? 0 : FNM_PATHNAME;\n+\n+\tif (explode_mod) {\n+\t\tchar buf[PATH_MAX * 2], *texts[PATH_MAX];\n+\t\tint pos = 0, cnt = 0, ndx = 0, len = strlen(text);\n+\n+\t\tif (empty_at_start)\n+\t\t\ttexts[ndx++] = \"\";\n+\t\t/* An empty string must turn into at least one empty array item. */\n+\t\twhile (1) {\n+\t\t\ttexts[ndx] = buf + ndx * (explode_mod + 1);\n+\t\t\tstrlcpy(texts[ndx++], text + pos, explode_mod + 1);\n+\t\t\tif (pos + explode_mod >= len)\n+\t\t\t\tbreak;\n+\t\t\tpos += explode_mod;\n+\t\t\tif (!(++cnt % empties_mod))\n+\t\t\t\ttexts[ndx++] = \"\";\n+\t\t}\n+\t\tif (empty_at_end)\n+\t\t\ttexts[ndx++] = \"\";\n+\t\ttexts[ndx] = NULL;\n+\t\tmatched = wildmatch_array(pattern, (const char**)texts, 0);\n+\t} else\n+\t\tmatched = wildmatch(pattern, text);\n+\tif (compare_fnmatch)\n+\t\tfn_matched = !fnmatch(pattern, text, flags);\n+\n+\tif (matched != matches) {\n+\t\tprintf(\"wildmatch failure on line %d:\\n  %s\\n  %s\\n  expected %s match\\n\",\n+\t\t       line, text, pattern, matches ? \"a\" : \"NO\");\n+\t\twildmatch_errors++;\n+\t}\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+\tif (compare_fnmatch && fn_matched != (matches ^ !same_as_fnmatch)) {\n+\t\tprintf(\"fnmatch disagreement on line %d:\\n  %s\\n  %s\\n  expected %s match\\n\",\n+\t\t       line, text, pattern, matches ^ !same_as_fnmatch ? \"a\" : \"NO\");\n+\t\tfnmatch_errors++;\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+int main(int argc, const 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+\tchar buf[2048];\n+\tFILE *fp;\n+\tint line;\n+\tconst struct option opts[] = {\n+\t\tOPT_INTEGER('E', \"empties\", &empties_mod, \"\"),\n+\t\tOPT_INTEGER('x', \"explode\", &explode_mod, \"explode mode\"),\n+\t\tOPT_BOOL('s', NULL, &empty_at_start, \"\"),\n+\t\tOPT_BOOL('e', NULL, &empty_at_end, \"\"),\n+\t\tOPT_BOOL(0, \"fnmatch\", &compare_fnmatch, \"compare with fnmatch()\"),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, opts, NULL, 0);\n+\tif (argc != 1)\n+\t\tdie(\"unknown option\");\n+\n+\tif (explode_mod && !empties_mod)\n+\t\tempties_mod = 1024;\n+\n+\tif ((fp = fopen(*argv, \"r\")) == NULL)\n+\t\tdie_errno(\"Unable to open %s\\n\", *argv);\n+\n+\tline = 0;\n+\twhile (fgets(buf, sizeof buf, fp)) {\n+\t\tchar *s, *string[2], *end[2];\n+\t\tint i, flag[2];\n+\n+\t\tline++;\n+\t\tif (*buf == '#' || *buf == '\\n')\n+\t\t\tcontinue;\n+\t\tfor (s = buf, i = 0; i <= 1; i++) {\n+\t\t\tif (*s == '1')\n+\t\t\t\tflag[i] = 1;\n+\t\t\telse if (*s == '0')\n+\t\t\t\tflag[i] = 0;\n+\t\t\telse\n+\t\t\t\tflag[i] = -1;\n+\t\t\tif (*++s != ' ' && *s != '\\t')\n+\t\t\t\tflag[i] = -1;\n+\t\t\tif (flag[i] < 0)\n+\t\t\t\tdie(\"Invalid flag syntax on line %d of %s:\\n%s\",\n+\t\t\t\t    line, *argv, buf);\n+\t\t\twhile (*++s == ' ' || *s == '\\t') {}\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\tfor (i = 0; i <= 1; i++) {\n+\t\t\tif (*s == '\\'' || *s == '\"' || *s == '`') {\n+\t\t\t\tchar quote = *s++;\n+\t\t\t\tstring[i] = s;\n+\t\t\t\twhile (*s && *s != quote) s++;\n+\t\t\t\tif (!*s)\n+\t\t\t\t\tdie(\"Unmatched quote on line %d of %s:\\n%s\",\n+\t\t\t\t\t    line, *argv, buf);\n+\t\t\t\tend[i] = s;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tif (!*s || *s == '\\n')\n+\t\t\t\t\tdie(\"Not enough strings on line %d of %s:\\n%s\",\n+\t\t\t\t\t    line, *argv, buf);\n+\t\t\t\tstring[i] = s;\n+\t\t\t\twhile (*++s && *s != ' ' && *s != '\\t' && *s != '\\n') {}\n+\t\t\t\tend[i] = s;\n+\t\t\t}\n+\t\t\twhile (*++s == ' ' || *s == '\\t') {}\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\t*end[0] = *end[1] = '\\0';\n+\t\trun_test(line, flag[0], flag[1], string[0], string[1]);\n \t}\n-\t*end[0] = *end[1] = '\\0';\n-\trun_test(line, flag[0], flag[1], 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+\tif (!wildmatch_errors)\n+\t\tfputs(\"No\", stdout);\n+\telse\n+\t\tprintf(\"%d\", wildmatch_errors);\n+\tprintf(\" wildmatch error%s found.\\n\", wildmatch_errors == 1 ? \"\" : \"s\");\n+\n+\tif (compare_fnmatch) {\n+\t\tif (!fnmatch_errors)\n+\t\t\tfputs(\"No\", stdout);\n+\t\telse\n+\t\t\tprintf(\"%d\", fnmatch_errors);\n+\t\tprintf(\" fnmatch error%s found.\\n\", fnmatch_errors == 1 ? \"\" : \"s\");\n+\t}\n+\treturn 0;\n }\n-- \n1.7.8.36.g69ee2\n"},{"id":"190995","messageId":"1336395717-7799-4-git-send-email-pclouds@gmail.com","threadId":"30458","inReplyTo":"1336395717-7799-1-git-send-email-pclouds@gmail.com","subject":"[PATCH/RFC 3/3] gitignore: support \"**\" with wildmatch()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-05-07T13:01:57Z","receivedAt":"2012-05-07T13:01:57Z","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 dir.c |   20 ++++++++++++++++----\n dir.h |    1 +\n 2 files changed, 17 insertions(+), 4 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex e98760c..75140f6 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 \"compat/wildmatch.h\"\n \n struct path_simplify {\n \tint len;\n@@ -335,6 +336,11 @@ void add_exclude(const char *string, const char *base,\n \t\tx->flags |= EXC_FLAG_NODIR;\n \tif (no_wildcard(string))\n \t\tx->flags |= EXC_FLAG_NOWILDCARD;\n+\telse if (strstr(string, \"**\")) {\n+\t\tif (ignore_case)\n+\t\t\twarning(_(\"core.ignorecase is ignored in '%s' because of \\\"**\\\"\"), string);\n+\t\tx->flags |= EXC_FLAG_WILDMATCH;\n+\t}\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@@ -549,11 +555,17 @@ int excluded_from_list(const char *pathname,\n \t\t\t\tif (x->flags & EXC_FLAG_NOWILDCARD) {\n \t\t\t\t\tif (!strcmp_icase(exclude, pathname + baselen))\n \t\t\t\t\t\treturn to_exclude;\n-\t\t\t\t} else {\n-\t\t\t\t\tif (fnmatch_icase(exclude, pathname+baselen,\n-\t\t\t\t\t\t    FNM_PATHNAME) == 0)\n-\t\t\t\t\t    return to_exclude;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (x->flags & EXC_FLAG_WILDMATCH) {\n+\t\t\t\t\tif (wildmatch(exclude, pathname + baselen))\n+\t\t\t\t\t\treturn to_exclude;\n+\t\t\t\t\tcontinue;\n \t\t\t\t}\n+\t\t\t\tif (fnmatch_icase(exclude, pathname + baselen,\n+\t\t\t\t\t\t  FNM_PATHNAME) == 0)\n+\t\t\t\t\treturn to_exclude;\n \t\t\t}\n \t\t}\n \t}\ndiff --git a/dir.h b/dir.h\nindex 58b6fc7..794f412 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -10,6 +10,7 @@ struct dir_entry {\n #define EXC_FLAG_NOWILDCARD 2\n #define EXC_FLAG_ENDSWITH 4\n #define EXC_FLAG_MUSTBEDIR 8\n+#define EXC_FLAG_WILDMATCH 16\n \n struct exclude_list {\n \tint nr;\n-- \n1.7.8.36.g69ee2\n"},{"id":"191026","messageId":"7vy5p34xso.fsf@alter.siamese.dyndns.org","threadId":"30458","inReplyTo":"1336395717-7799-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH/RFC 0/3] Support ignore rule \"**\" using wildmatch from rsync","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-05-07T19:05:11Z","receivedAt":"2012-05-07T19:05:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I skimmed the series and I think it would be a good change in the longer\nterm, but I suspect we probably have an unfinished business on the core\nside that sometimes uses pathspec->raw field and assume that they only\nneed to do the leading-path match, which needs to be fixed first before\nchanges like this can be sanely added.\n"}]}