git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/5] wildmatch series update
@ 2012-09-26 11:25 Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
                   ` (5 more replies)
  0 siblings, 6 replies; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

No functional changes. Just incorporate changes from Ramsay and Johannes.

Nguyễn Thái Ngọc Duy (5):
  Import wildmatch from rsync
  compat/wildmatch: remove static variable force_lower_case
  compat/wildmatch: fix case-insensitive matching
  Integrate wildmatch to git
  Support "**" in .gitignore and .gitattributes patterns using
    wildmatch()

 .gitignore                         |   1 +
 Documentation/gitignore.txt        |   3 +
 Makefile                           |   3 +
 attr.c                             |   4 +-
 dir.c                              |   5 +-
 t/t0003-attributes.sh              |  17 ++
 t/t3001-ls-files-others-exclude.sh |  11 ++
 t/t3070-wildmatch.sh               |  27 +++
 t/t3070/wildtest.txt               | 165 +++++++++++++++++
 test-wildmatch.c                   | 208 ++++++++++++++++++++++
 wildmatch.c                        | 355 +++++++++++++++++++++++++++++++++++++
 wildmatch.h                        |   6 +
 12 files changed, 803 insertions(+), 2 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh
 create mode 100644 t/t3070/wildtest.txt
 create mode 100644 test-wildmatch.c
 create mode 100644 wildmatch.c
 create mode 100644 wildmatch.h

-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/5] Import wildmatch from rsync
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
@ 2012-09-26 11:25 ` Nguyễn Thái Ngọc Duy
  2012-10-02 14:07   ` Ævar Arnfjörð Bjarmason
  2012-09-26 11:25 ` [PATCH 2/5] compat/wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=UTF-8, Size: 22843 bytes --]

These files are from rsync.git commit
f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit
before rsync turned GPL-3. All files are imported as-is and
no-op. Adaptation is done in a separate patch.

rsync.git           ->  git.git
lib/wildmatch.[ch]      wildmatch.[ch]
wildtest.c              test-wildmatch.c
wildtest.txt            t/t3070/wildtest.txt

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t3070/wildtest.txt | 165 +++++++++++++++++++++++
 test-wildmatch.c     | 222 +++++++++++++++++++++++++++++++
 wildmatch.c          | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++
 wildmatch.h          |   6 +
 4 files changed, 761 insertions(+)
 create mode 100644 t/t3070/wildtest.txt
 create mode 100644 test-wildmatch.c
 create mode 100644 wildmatch.c
 create mode 100644 wildmatch.h

diff --git a/t/t3070/wildtest.txt b/t/t3070/wildtest.txt
new file mode 100644
index 0000000..42c1678
--- /dev/null
+++ b/t/t3070/wildtest.txt
@@ -0,0 +1,165 @@
+# Input is in the following format (all items white-space separated):
+#
+# The first two items are 1 or 0 indicating if the wildmat call is expected to
+# succeed and if fnmatch works the same way as wildmat, respectively.  After
+# that is a text string for the match, and a pattern string.  Strings can be
+# quoted (if desired) in either double or single quotes, as well as backticks.
+#
+# MATCH FNMATCH_SAME "text to match" 'pattern to use'
+
+# Basic wildmat features
+1 1 foo			foo
+0 1 foo			bar
+1 1 ''			""
+1 1 foo			???
+0 1 foo			??
+1 1 foo			*
+1 1 foo			f*
+0 1 foo			*f
+1 1 foo			*foo*
+1 1 foobar		*ob*a*r*
+1 1 aaaaaaabababab	*ab
+1 1 foo*		foo\*
+0 1 foobar		foo\*bar
+1 1 f\oo		f\\oo
+1 1 ball		*[al]?
+0 1 ten			[ten]
+1 1 ten			**[!te]
+0 1 ten			**[!ten]
+1 1 ten			t[a-g]n
+0 1 ten			t[!a-g]n
+1 1 ton			t[!a-g]n
+1 1 ton			t[^a-g]n
+1 1 a]b			a[]]b
+1 1 a-b			a[]-]b
+1 1 a]b			a[]-]b
+0 1 aab			a[]-]b
+1 1 aab			a[]a-]b
+1 1 ]			]
+
+# Extended slash-matching features
+0 1 foo/baz/bar		foo*bar
+1 1 foo/baz/bar		foo**bar
+0 1 foo/bar		foo?bar
+0 1 foo/bar		foo[/]bar
+0 1 foo/bar		f[^eiu][^eiu][^eiu][^eiu][^eiu]r
+1 1 foo-bar		f[^eiu][^eiu][^eiu][^eiu][^eiu]r
+0 1 foo			**/foo
+1 1 /foo		**/foo
+1 1 bar/baz/foo		**/foo
+0 1 bar/baz/foo		*/foo
+0 0 foo/bar/baz		**/bar*
+1 1 deep/foo/bar/baz	**/bar/*
+0 1 deep/foo/bar/baz/	**/bar/*
+1 1 deep/foo/bar/baz/	**/bar/**
+0 1 deep/foo/bar	**/bar/*
+1 1 deep/foo/bar/	**/bar/**
+1 1 foo/bar/baz		**/bar**
+1 1 foo/bar/baz/x	*/bar/**
+0 0 deep/foo/bar/baz/x	*/bar/**
+1 1 deep/foo/bar/baz/x	**/bar/*/*
+
+# Various additional tests
+0 1 acrt		a[c-c]st
+1 1 acrt		a[c-c]rt
+0 1 ]			[!]-]
+1 1 a			[!]-]
+0 1 ''			\
+0 1 \			\
+0 1 /\			*/\
+1 1 /\			*/\\
+1 1 foo			foo
+1 1 @foo		@foo
+0 1 foo			@foo
+1 1 [ab]		\[ab]
+1 1 [ab]		[[]ab]
+1 1 [ab]		[[:]ab]
+0 1 [ab]		[[::]ab]
+1 1 [ab]		[[:digit]ab]
+1 1 [ab]		[\[:]ab]
+1 1 ?a?b		\??\?b
+1 1 abc			\a\b\c
+0 1 foo			''
+1 1 foo/bar/baz/to	**/t[o]
+
+# Character class tests
+1 1 a1B		[[:alpha:]][[:digit:]][[:upper:]]
+0 1 a		[[:digit:][:upper:][:space:]]
+1 1 A		[[:digit:][:upper:][:space:]]
+1 1 1		[[:digit:][:upper:][:space:]]
+0 1 1		[[:digit:][:upper:][:spaci:]]
+1 1 ' '		[[:digit:][:upper:][:space:]]
+0 1 .		[[:digit:][:upper:][:space:]]
+1 1 .		[[:digit:][:punct:][:space:]]
+1 1 5		[[:xdigit:]]
+1 1 f		[[:xdigit:]]
+1 1 D		[[:xdigit:]]
+1 1 _		[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+#1 1 …		[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+1 1 \x7f		[^[:alnum:][:alpha:][:blank:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+1 1 .		[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]
+1 1 5		[a-c[:digit:]x-z]
+1 1 b		[a-c[:digit:]x-z]
+1 1 y		[a-c[:digit:]x-z]
+0 1 q		[a-c[:digit:]x-z]
+
+# Additional tests, including some malformed wildmats
+1 1 ]		[\\-^]
+0 1 [		[\\-^]
+1 1 -		[\-_]
+1 1 ]		[\]]
+0 1 \]		[\]]
+0 1 \		[\]]
+0 1 ab		a[]b
+0 1 a[]b	a[]b
+0 1 ab[		ab[
+0 1 ab		[!
+0 1 ab		[-
+1 1 -		[-]
+0 1 -		[a-
+0 1 -		[!a-
+1 1 -		[--A]
+1 1 5		[--A]
+1 1 ' '		'[ --]'
+1 1 $		'[ --]'
+1 1 -		'[ --]'
+0 1 0		'[ --]'
+1 1 -		[---]
+1 1 -		[------]
+0 1 j		[a-e-n]
+1 1 -		[a-e-n]
+1 1 a		[!------]
+0 1 [		[]-a]
+1 1 ^		[]-a]
+0 1 ^		[!]-a]
+1 1 [		[!]-a]
+1 1 ^		[a^bc]
+1 1 -b]		[a-]b]
+0 1 \		[\]
+1 1 \		[\\]
+0 1 \		[!\\]
+1 1 G		[A-\\]
+0 1 aaabbb	b*a
+0 1 aabcaa	*ba*
+1 1 ,		[,]
+1 1 ,		[\\,]
+1 1 \		[\\,]
+1 1 -		[,-.]
+0 1 +		[,-.]
+0 1 -.]		[,-.]
+1 1 2		[\1-\3]
+1 1 3		[\1-\3]
+0 1 4		[\1-\3]
+1 1 \		[[-\]]
+1 1 [		[[-\]]
+1 1 ]		[[-\]]
+0 1 -		[[-\]]
+
+# Test recursion and the abort code (use "wildtest -i" to see iteration counts)
+1 1 -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+0 1 -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+0 1 -adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+1 1 /adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1	/*/*/*/*/*/*/12/*/*/*/m/*/*/*
+0 1 /adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1	/*/*/*/*/*/*/12/*/*/*/m/*/*/*
+1 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt		**/*a*b*g*n*t
+0 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz		**/*a*b*g*n*t
diff --git a/test-wildmatch.c b/test-wildmatch.c
new file mode 100644
index 0000000..88585c2
--- /dev/null
+++ b/test-wildmatch.c
@@ -0,0 +1,222 @@
+/*
+ * Test suite for the wildmatch code.
+ *
+ * Copyright (C) 2003-2009 Wayne Davison
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, visit the http://fsf.org website.
+ */
+
+/*#define COMPARE_WITH_FNMATCH*/
+
+#define WILD_TEST_ITERATIONS
+#include "lib/wildmatch.c"
+
+#include <popt.h>
+
+#ifdef COMPARE_WITH_FNMATCH
+#include <fnmatch.h>
+
+int fnmatch_errors = 0;
+#endif
+
+int wildmatch_errors = 0;
+char number_separator = ',';
+
+typedef char bool;
+
+int output_iterations = 0;
+int explode_mod = 0;
+int empties_mod = 0;
+int empty_at_start = 0;
+int empty_at_end = 0;
+
+static struct poptOption long_options[] = {
+  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
+  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
+  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
+  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
+  {0,0,0,0, 0, 0, 0}
+};
+
+/* match just at the start of string (anchored tests) */
+static void
+run_test(int line, bool matches,
+#ifdef COMPARE_WITH_FNMATCH
+	 bool same_as_fnmatch,
+#endif
+	 const char *text, const char *pattern)
+{
+    bool matched;
+#ifdef COMPARE_WITH_FNMATCH
+    bool fn_matched;
+    int flags = strstr(pattern, "**")? 0 : FNM_PATHNAME;
+#endif
+
+    if (explode_mod) {
+	char buf[MAXPATHLEN*2], *texts[MAXPATHLEN];
+	int pos = 0, cnt = 0, ndx = 0, len = strlen(text);
+
+	if (empty_at_start)
+	    texts[ndx++] = "";
+	/* An empty string must turn into at least one empty array item. */
+	while (1) {
+	    texts[ndx] = buf + ndx * (explode_mod + 1);
+	    strlcpy(texts[ndx++], text + pos, explode_mod + 1);
+	    if (pos + explode_mod >= len)
+		break;
+	    pos += explode_mod;
+	    if (!(++cnt % empties_mod))
+		texts[ndx++] = "";
+	}
+	if (empty_at_end)
+	    texts[ndx++] = "";
+	texts[ndx] = NULL;
+	matched = wildmatch_array(pattern, (const char**)texts, 0);
+    } else
+	matched = wildmatch(pattern, text);
+#ifdef COMPARE_WITH_FNMATCH
+    fn_matched = !fnmatch(pattern, text, flags);
+#endif
+    if (matched != matches) {
+	printf("wildmatch failure on line %d:\n  %s\n  %s\n  expected %s match\n",
+	       line, text, pattern, matches? "a" : "NO");
+	wildmatch_errors++;
+    }
+#ifdef COMPARE_WITH_FNMATCH
+    if (fn_matched != (matches ^ !same_as_fnmatch)) {
+	printf("fnmatch disagreement on line %d:\n  %s\n  %s\n  expected %s match\n",
+	       line, text, pattern, matches ^ !same_as_fnmatch? "a" : "NO");
+	fnmatch_errors++;
+    }
+#endif
+    if (output_iterations) {
+	printf("%d: \"%s\" iterations = %d\n", line, pattern,
+	       wildmatch_iteration_count);
+    }
+}
+
+int
+main(int argc, char **argv)
+{
+    char buf[2048], *s, *string[2], *end[2];
+    const char *arg;
+    FILE *fp;
+    int opt, line, i, flag[2];
+    poptContext pc = poptGetContext("wildtest", argc, (const char**)argv,
+				    long_options, 0);
+
+    while ((opt = poptGetNextOpt(pc)) != -1) {
+	switch (opt) {
+	  case 'e':
+	    arg = poptGetOptArg(pc);
+	    empties_mod = atoi(arg);
+	    if (strchr(arg, 's'))
+		empty_at_start = 1;
+	    if (strchr(arg, 'e'))
+		empty_at_end = 1;
+	    if (!explode_mod)
+		explode_mod = 1024;
+	    break;
+	  default:
+	    fprintf(stderr, "%s: %s\n",
+		    poptBadOption(pc, POPT_BADOPTION_NOALIAS),
+		    poptStrerror(opt));
+	    exit(1);
+	}
+    }
+
+    if (explode_mod && !empties_mod)
+	empties_mod = 1024;
+
+    argv = (char**)poptGetArgs(pc);
+    if (!argv || argv[1]) {
+	fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n");
+	exit(1);
+    }
+
+    if ((fp = fopen(*argv, "r")) == NULL) {
+	fprintf(stderr, "Unable to open %s\n", *argv);
+	exit(1);
+    }
+
+    line = 0;
+    while (fgets(buf, sizeof buf, fp)) {
+	line++;
+	if (*buf == '#' || *buf == '\n')
+	    continue;
+	for (s = buf, i = 0; i <= 1; i++) {
+	    if (*s == '1')
+		flag[i] = 1;
+	    else if (*s == '0')
+		flag[i] = 0;
+	    else
+		flag[i] = -1;
+	    if (*++s != ' ' && *s != '\t')
+		flag[i] = -1;
+	    if (flag[i] < 0) {
+		fprintf(stderr, "Invalid flag syntax on line %d of %s:\n%s",
+			line, *argv, buf);
+		exit(1);
+	    }
+	    while (*++s == ' ' || *s == '\t') {}
+	}
+	for (i = 0; i <= 1; i++) {
+	    if (*s == '\'' || *s == '"' || *s == '`') {
+		char quote = *s++;
+		string[i] = s;
+		while (*s && *s != quote) s++;
+		if (!*s) {
+		    fprintf(stderr, "Unmatched quote on line %d of %s:\n%s",
+			    line, *argv, buf);
+		    exit(1);
+		}
+		end[i] = s;
+	    }
+	    else {
+		if (!*s || *s == '\n') {
+		    fprintf(stderr, "Not enough strings on line %d of %s:\n%s",
+			    line, *argv, buf);
+		    exit(1);
+		}
+		string[i] = s;
+		while (*++s && *s != ' ' && *s != '\t' && *s != '\n') {}
+		end[i] = s;
+	    }
+	    while (*++s == ' ' || *s == '\t') {}
+	}
+	*end[0] = *end[1] = '\0';
+	run_test(line, flag[0],
+#ifdef COMPARE_WITH_FNMATCH
+		 flag[1],
+#endif
+		 string[0], string[1]);
+    }
+
+    if (!wildmatch_errors)
+	fputs("No", stdout);
+    else
+	printf("%d", wildmatch_errors);
+    printf(" wildmatch error%s found.\n", wildmatch_errors == 1? "" : "s");
+
+#ifdef COMPARE_WITH_FNMATCH
+    if (!fnmatch_errors)
+	fputs("No", stdout);
+    else
+	printf("%d", fnmatch_errors);
+    printf(" fnmatch error%s found.\n", fnmatch_errors == 1? "" : "s");
+
+#endif
+
+    return 0;
+}
diff --git a/wildmatch.c b/wildmatch.c
new file mode 100644
index 0000000..f3a1731
--- /dev/null
+++ b/wildmatch.c
@@ -0,0 +1,368 @@
+/*
+**  Do shell-style pattern matching for ?, \, [], and * characters.
+**  It is 8bit clean.
+**
+**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+**  Rich $alz is now <rsalz@bbn.com>.
+**
+**  Modified by Wayne Davison to special-case '/' matching, to make '**'
+**  work differently than '*', and to fix the character-class code.
+*/
+
+#include "rsync.h"
+
+/* What character marks an inverted character class? */
+#define NEGATE_CLASS	'!'
+#define NEGATE_CLASS2	'^'
+
+#define FALSE 0
+#define TRUE 1
+#define ABORT_ALL -1
+#define ABORT_TO_STARSTAR -2
+
+#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
+				    && *(class) == *(litmatch) \
+				    && strncmp((char*)class, litmatch, len) == 0)
+
+#if defined STDC_HEADERS || !defined isascii
+# define ISASCII(c) 1
+#else
+# define ISASCII(c) isascii(c)
+#endif
+
+#ifdef isblank
+# define ISBLANK(c) (ISASCII(c) && isblank(c))
+#else
+# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifdef isgraph
+# define ISGRAPH(c) (ISASCII(c) && isgraph(c))
+#else
+# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))
+#endif
+
+#define ISPRINT(c) (ISASCII(c) && isprint(c))
+#define ISDIGIT(c) (ISASCII(c) && isdigit(c))
+#define ISALNUM(c) (ISASCII(c) && isalnum(c))
+#define ISALPHA(c) (ISASCII(c) && isalpha(c))
+#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))
+#define ISLOWER(c) (ISASCII(c) && islower(c))
+#define ISPUNCT(c) (ISASCII(c) && ispunct(c))
+#define ISSPACE(c) (ISASCII(c) && isspace(c))
+#define ISUPPER(c) (ISASCII(c) && isupper(c))
+#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
+
+#ifdef WILD_TEST_ITERATIONS
+int wildmatch_iteration_count;
+#endif
+
+static int force_lower_case = 0;
+
+/* Match pattern "p" against the a virtually-joined string consisting
+ * of "text" and any strings in array "a". */
+static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
+{
+    uchar p_ch;
+
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count++;
+#endif
+
+    for ( ; (p_ch = *p) != '\0'; text++, p++) {
+	int matched, special;
+	uchar t_ch, prev_ch;
+	while ((t_ch = *text) == '\0') {
+	    if (*a == NULL) {
+		if (p_ch != '*')
+		    return ABORT_ALL;
+		break;
+	    }
+	    text = *a++;
+	}
+	if (force_lower_case && ISUPPER(t_ch))
+	    t_ch = tolower(t_ch);
+	switch (p_ch) {
+	  case '\\':
+	    /* Literal match with following character.  Note that the test
+	     * in "default" handles the p[1] == '\0' failure case. */
+	    p_ch = *++p;
+	    /* FALLTHROUGH */
+	  default:
+	    if (t_ch != p_ch)
+		return FALSE;
+	    continue;
+	  case '?':
+	    /* Match anything but '/'. */
+	    if (t_ch == '/')
+		return FALSE;
+	    continue;
+	  case '*':
+	    if (*++p == '*') {
+		while (*++p == '*') {}
+		special = TRUE;
+	    } else
+		special = FALSE;
+	    if (*p == '\0') {
+		/* Trailing "**" matches everything.  Trailing "*" matches
+		 * only if there are no more slash characters. */
+		if (!special) {
+		    do {
+			if (strchr((char*)text, '/') != NULL)
+			    return FALSE;
+		    } while ((text = *a++) != NULL);
+		}
+		return TRUE;
+	    }
+	    while (1) {
+		if (t_ch == '\0') {
+		    if ((text = *a++) == NULL)
+			break;
+		    t_ch = *text;
+		    continue;
+		}
+		if ((matched = dowild(p, text, a)) != FALSE) {
+		    if (!special || matched != ABORT_TO_STARSTAR)
+			return matched;
+		} else if (!special && t_ch == '/')
+		    return ABORT_TO_STARSTAR;
+		t_ch = *++text;
+	    }
+	    return ABORT_ALL;
+	  case '[':
+	    p_ch = *++p;
+#ifdef NEGATE_CLASS2
+	    if (p_ch == NEGATE_CLASS2)
+		p_ch = NEGATE_CLASS;
+#endif
+	    /* Assign literal TRUE/FALSE because of "matched" comparison. */
+	    special = p_ch == NEGATE_CLASS? TRUE : FALSE;
+	    if (special) {
+		/* Inverted character class. */
+		p_ch = *++p;
+	    }
+	    prev_ch = 0;
+	    matched = FALSE;
+	    do {
+		if (!p_ch)
+		    return ABORT_ALL;
+		if (p_ch == '\\') {
+		    p_ch = *++p;
+		    if (!p_ch)
+			return ABORT_ALL;
+		    if (t_ch == p_ch)
+			matched = TRUE;
+		} else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
+		    p_ch = *++p;
+		    if (p_ch == '\\') {
+			p_ch = *++p;
+			if (!p_ch)
+			    return ABORT_ALL;
+		    }
+		    if (t_ch <= p_ch && t_ch >= prev_ch)
+			matched = TRUE;
+		    p_ch = 0; /* This makes "prev_ch" get set to 0. */
+		} else if (p_ch == '[' && p[1] == ':') {
+		    const uchar *s;
+		    int i;
+		    for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
+		    if (!p_ch)
+			return ABORT_ALL;
+		    i = p - s - 1;
+		    if (i < 0 || p[-1] != ':') {
+			/* Didn't find ":]", so treat like a normal set. */
+			p = s - 2;
+			p_ch = '[';
+			if (t_ch == p_ch)
+			    matched = TRUE;
+			continue;
+		    }
+		    if (CC_EQ(s,i, "alnum")) {
+			if (ISALNUM(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "alpha")) {
+			if (ISALPHA(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "blank")) {
+			if (ISBLANK(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "cntrl")) {
+			if (ISCNTRL(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "digit")) {
+			if (ISDIGIT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "graph")) {
+			if (ISGRAPH(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "lower")) {
+			if (ISLOWER(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "print")) {
+			if (ISPRINT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "punct")) {
+			if (ISPUNCT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "space")) {
+			if (ISSPACE(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "upper")) {
+			if (ISUPPER(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "xdigit")) {
+			if (ISXDIGIT(t_ch))
+			    matched = TRUE;
+		    } else /* malformed [:class:] string */
+			return ABORT_ALL;
+		    p_ch = 0; /* This makes "prev_ch" get set to 0. */
+		} else if (t_ch == p_ch)
+		    matched = TRUE;
+	    } while (prev_ch = p_ch, (p_ch = *++p) != ']');
+	    if (matched == special || t_ch == '/')
+		return FALSE;
+	    continue;
+	}
+    }
+
+    do {
+	if (*text)
+	    return FALSE;
+    } while ((text = *a++) != NULL);
+
+    return TRUE;
+}
+
+/* Match literal string "s" against the a virtually-joined string consisting
+ * of "text" and any strings in array "a". */
+static int doliteral(const uchar *s, const uchar *text, const uchar*const *a)
+{
+    for ( ; *s != '\0'; text++, s++) {
+	while (*text == '\0') {
+	    if ((text = *a++) == NULL)
+		return FALSE;
+	}
+	if (*text != *s)
+	    return FALSE;
+    }
+
+    do {
+	if (*text)
+	    return FALSE;
+    } while ((text = *a++) != NULL);
+
+    return TRUE;
+}
+
+/* Return the last "count" path elements from the concatenated string.
+ * We return a string pointer to the start of the string, and update the
+ * array pointer-pointer to point to any remaining string elements. */
+static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)
+{
+    const uchar*const *a = *a_ptr;
+    const uchar*const *first_a = a;
+
+    while (*a)
+	    a++;
+
+    while (a != first_a) {
+	const uchar *s = *--a;
+	s += strlen((char*)s);
+	while (--s >= *a) {
+	    if (*s == '/' && !--count) {
+		*a_ptr = a+1;
+		return s+1;
+	    }
+	}
+    }
+
+    if (count == 1) {
+	*a_ptr = a+1;
+	return *a;
+    }
+
+    return NULL;
+}
+
+/* Match the "pattern" against the "text" string. */
+int wildmatch(const char *pattern, const char *text)
+{
+    static const uchar *nomore[1]; /* A NULL pointer. */
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
+}
+
+/* Match the "pattern" against the forced-to-lower-case "text" string. */
+int iwildmatch(const char *pattern, const char *text)
+{
+    static const uchar *nomore[1]; /* A NULL pointer. */
+    int ret;
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+    force_lower_case = 1;
+    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
+    force_lower_case = 0;
+    return ret;
+}
+
+/* Match pattern "p" against the a virtually-joined string consisting
+ * of all the pointers in array "texts" (which has a NULL pointer at the
+ * end).  The int "where" can be 0 (normal matching), > 0 (match only
+ * the trailing N slash-separated filename components of "texts"), or < 0
+ * (match the "pattern" at the start or after any slash in "texts"). */
+int wildmatch_array(const char *pattern, const char*const *texts, int where)
+{
+    const uchar *p = (const uchar*)pattern;
+    const uchar*const *a = (const uchar*const*)texts;
+    const uchar *text;
+    int matched;
+
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+
+    if (where > 0)
+	text = trailing_N_elements(&a, where);
+    else
+	text = *a++;
+    if (!text)
+	return FALSE;
+
+    if ((matched = dowild(p, text, a)) != TRUE && where < 0
+     && matched != ABORT_ALL) {
+	while (1) {
+	    if (*text == '\0') {
+		if ((text = (uchar*)*a++) == NULL)
+		    return FALSE;
+		continue;
+	    }
+	    if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE
+	     && matched != ABORT_TO_STARSTAR)
+		break;
+	}
+    }
+    return matched == TRUE;
+}
+
+/* Match literal string "s" against the a virtually-joined string consisting
+ * of all the pointers in array "texts" (which has a NULL pointer at the
+ * end).  The int "where" can be 0 (normal matching), or > 0 (match
+ * only the trailing N slash-separated filename components of "texts"). */
+int litmatch_array(const char *string, const char*const *texts, int where)
+{
+    const uchar *s = (const uchar*)string;
+    const uchar*const *a = (const uchar* const*)texts;
+    const uchar *text;
+
+    if (where > 0)
+	text = trailing_N_elements(&a, where);
+    else
+	text = *a++;
+    if (!text)
+	return FALSE;
+
+    return doliteral(s, text, a) == TRUE;
+}
diff --git a/wildmatch.h b/wildmatch.h
new file mode 100644
index 0000000..e7f1a35
--- /dev/null
+++ b/wildmatch.h
@@ -0,0 +1,6 @@
+/* wildmatch.h */
+
+int wildmatch(const char *pattern, const char *text);
+int iwildmatch(const char *pattern, const char *text);
+int wildmatch_array(const char *pattern, const char*const *texts, int where);
+int litmatch_array(const char *string, const char*const *texts, int where);
-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 2/5] compat/wildmatch: remove static variable force_lower_case
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
@ 2012-09-26 11:25 ` Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 3/5] compat/wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

One place less to worry about thread safety

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 wildmatch.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/wildmatch.c b/wildmatch.c
index f3a1731..e824eb2 100644
--- a/wildmatch.c
+++ b/wildmatch.c
@@ -57,11 +57,10 @@
 int wildmatch_iteration_count;
 #endif
 
-static int force_lower_case = 0;
-
 /* Match pattern "p" against the a virtually-joined string consisting
  * of "text" and any strings in array "a". */
-static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
+static int dowild(const uchar *p, const uchar *text,
+		  const uchar*const *a, int force_lower_case)
 {
     uchar p_ch;
 
@@ -121,7 +120,7 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
 		    t_ch = *text;
 		    continue;
 		}
-		if ((matched = dowild(p, text, a)) != FALSE) {
+		if ((matched = dowild(p, text, a, force_lower_case)) != FALSE) {
 		    if (!special || matched != ABORT_TO_STARSTAR)
 			return matched;
 		} else if (!special && t_ch == '/')
@@ -291,7 +290,7 @@ int wildmatch(const char *pattern, const char *text)
 #ifdef WILD_TEST_ITERATIONS
     wildmatch_iteration_count = 0;
 #endif
-    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
+    return dowild((const uchar*)pattern, (const uchar*)text, nomore, 0) == TRUE;
 }
 
 /* Match the "pattern" against the forced-to-lower-case "text" string. */
@@ -302,9 +301,7 @@ int iwildmatch(const char *pattern, const char *text)
 #ifdef WILD_TEST_ITERATIONS
     wildmatch_iteration_count = 0;
 #endif
-    force_lower_case = 1;
-    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
-    force_lower_case = 0;
+    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore, 1) == TRUE;
     return ret;
 }
 
@@ -331,7 +328,7 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)
     if (!text)
 	return FALSE;
 
-    if ((matched = dowild(p, text, a)) != TRUE && where < 0
+    if ((matched = dowild(p, text, a, 0)) != TRUE && where < 0
      && matched != ABORT_ALL) {
 	while (1) {
 	    if (*text == '\0') {
@@ -339,7 +336,7 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)
 		    return FALSE;
 		continue;
 	    }
-	    if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE
+	    if (*text++ == '/' && (matched = dowild(p, text, a, 0)) != FALSE
 	     && matched != ABORT_TO_STARSTAR)
 		break;
 	}
-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 3/5] compat/wildmatch: fix case-insensitive matching
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 2/5] compat/wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
@ 2012-09-26 11:25 ` Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 4/5] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

dowild() does case insensitive matching by lower-casing the text. That
means lower case letters in patterns imply case-insensitive matching,
but upper case means exact matching.

We do not want that subtlety. Lower case pattern too so iwildmatch()
always does what we expect it to do.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 wildmatch.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/wildmatch.c b/wildmatch.c
index e824eb2..c7f7f9f 100644
--- a/wildmatch.c
+++ b/wildmatch.c
@@ -81,6 +81,8 @@ static int dowild(const uchar *p, const uchar *text,
 	}
 	if (force_lower_case && ISUPPER(t_ch))
 	    t_ch = tolower(t_ch);
+	if (force_lower_case && ISUPPER(p_ch))
+	    p_ch = tolower(p_ch);
 	switch (p_ch) {
 	  case '\\':
 	    /* Literal match with following character.  Note that the test
-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 4/5] Integrate wildmatch to git
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
                   ` (2 preceding siblings ...)
  2012-09-26 11:25 ` [PATCH 3/5] compat/wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
@ 2012-09-26 11:25 ` Nguyễn Thái Ngọc Duy
  2012-09-26 11:25 ` [PATCH 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
  2012-09-27 18:08 ` [PATCH 0/5] wildmatch series update Junio C Hamano
  5 siblings, 0 replies; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy, Ramsay Jones

This makes wildmatch.c part of libgit.a and builds test-wildmatch; the
dependency on libpopt in the original has been replaced with the use
of our parse-options. Global variables in test-wildmatch are marked
static to avoid sparse warnings.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 .gitignore           |  1 +
 Makefile             |  3 ++
 t/t3070-wildmatch.sh | 27 ++++++++++++++++
 test-wildmatch.c     | 88 ++++++++++++++++++++++------------------------------
 wildmatch.c          | 26 +++++-----------
 5 files changed, 75 insertions(+), 70 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh

diff --git a/.gitignore b/.gitignore
index 68fe464..54b1b3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -196,6 +196,7 @@
 /test-sigchain
 /test-subprocess
 /test-svn-fe
+/test-wildmatch
 /common-cmds.h
 *.tar.gz
 *.dsc
diff --git a/Makefile b/Makefile
index 26b697d..d6235e6 100644
--- a/Makefile
+++ b/Makefile
@@ -504,6 +504,7 @@ TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
+TEST_PROGRAMS_NEED_X += test-wildmatch
 
 TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
 
@@ -676,6 +677,7 @@ LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += varint.h
 LIB_H += walker.h
+LIB_H += wildmatch.h
 LIB_H += wt-status.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
@@ -807,6 +809,7 @@ LIB_OBJS += utf8.o
 LIB_OBJS += varint.o
 LIB_OBJS += version.o
 LIB_OBJS += walker.o
+LIB_OBJS += wildmatch.o
 LIB_OBJS += wrapper.o
 LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
new file mode 100755
index 0000000..c4da26c
--- /dev/null
+++ b/t/t3070-wildmatch.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+test_description='wildmatch tests'
+
+. ./test-lib.sh
+
+test_wildmatch() {
+    test_expect_success "wildmatch $*" "
+	test-wildmatch $* ../t3070/wildtest.txt >actual &&
+	echo 'No wildmatch errors found.' >expected &&
+	test_cmp expected actual
+    "
+}
+
+test_wildmatch -x1
+test_wildmatch -x1 -e1
+test_wildmatch -x1 -else
+test_wildmatch -x2
+test_wildmatch -x2 -ese
+test_wildmatch -x3
+test_wildmatch -x3 -e1
+test_wildmatch -x4
+test_wildmatch -x4 -e2e
+test_wildmatch -x5
+test_wildmatch -x5 -es
+
+test_done
diff --git a/test-wildmatch.c b/test-wildmatch.c
index 88585c2..bb726c8 100644
--- a/test-wildmatch.c
+++ b/test-wildmatch.c
@@ -19,34 +19,38 @@
 
 /*#define COMPARE_WITH_FNMATCH*/
 
-#define WILD_TEST_ITERATIONS
-#include "lib/wildmatch.c"
+#include "cache.h"
+#include "parse-options.h"
+#include "wildmatch.h"
 
-#include <popt.h>
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 1024
+#endif
+#ifdef NO_STRLCPY
+#include "compat/strlcpy.c"
+#define strlcpy gitstrlcpy
+#endif
 
 #ifdef COMPARE_WITH_FNMATCH
 #include <fnmatch.h>
 
-int fnmatch_errors = 0;
+static int fnmatch_errors = 0;
 #endif
 
-int wildmatch_errors = 0;
-char number_separator = ',';
+static int wildmatch_errors = 0;
 
 typedef char bool;
 
-int output_iterations = 0;
-int explode_mod = 0;
-int empties_mod = 0;
-int empty_at_start = 0;
-int empty_at_end = 0;
-
-static struct poptOption long_options[] = {
-  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
-  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
-  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
-  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
-  {0,0,0,0, 0, 0, 0}
+static int explode_mod = 0;
+static int empties_mod = 0;
+static int empty_at_start = 0;
+static int empty_at_end = 0;
+static char *empties;
+
+static struct option options[] = {
+  OPT_STRING('e', "empties", &empties, "", ""),
+  OPT_INTEGER('x', "explode", &explode_mod, ""),
+  OPT_END(),
 };
 
 /* match just at the start of string (anchored tests) */
@@ -100,51 +104,33 @@ run_test(int line, bool matches,
 	fnmatch_errors++;
     }
 #endif
-    if (output_iterations) {
-	printf("%d: \"%s\" iterations = %d\n", line, pattern,
-	       wildmatch_iteration_count);
-    }
 }
 
 int
 main(int argc, char **argv)
 {
     char buf[2048], *s, *string[2], *end[2];
-    const char *arg;
     FILE *fp;
-    int opt, line, i, flag[2];
-    poptContext pc = poptGetContext("wildtest", argc, (const char**)argv,
-				    long_options, 0);
-
-    while ((opt = poptGetNextOpt(pc)) != -1) {
-	switch (opt) {
-	  case 'e':
-	    arg = poptGetOptArg(pc);
-	    empties_mod = atoi(arg);
-	    if (strchr(arg, 's'))
-		empty_at_start = 1;
-	    if (strchr(arg, 'e'))
-		empty_at_end = 1;
-	    if (!explode_mod)
-		explode_mod = 1024;
-	    break;
-	  default:
-	    fprintf(stderr, "%s: %s\n",
-		    poptBadOption(pc, POPT_BADOPTION_NOALIAS),
-		    poptStrerror(opt));
-	    exit(1);
-	}
+    int line, i, flag[2];
+    const char *help[] = { NULL };
+
+    argc = parse_options(argc, (const char **)argv, "", options, help, 0);
+    if (argc != 1)
+	    die("redundant options");
+    if (empties) {
+	const char *arg = empties;
+	empties_mod = atoi(arg);
+	if (strchr(empties, 's'))
+	    empty_at_start = 1;
+	if (strchr(arg, 'e'))
+	    empty_at_end = 1;
+	if (!explode_mod)
+	    explode_mod = 1024;
     }
 
     if (explode_mod && !empties_mod)
 	empties_mod = 1024;
 
-    argv = (char**)poptGetArgs(pc);
-    if (!argv || argv[1]) {
-	fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n");
-	exit(1);
-    }
-
     if ((fp = fopen(*argv, "r")) == NULL) {
 	fprintf(stderr, "Unable to open %s\n", *argv);
 	exit(1);
diff --git a/wildmatch.c b/wildmatch.c
index c7f7f9f..f153f8a 100644
--- a/wildmatch.c
+++ b/wildmatch.c
@@ -9,7 +9,13 @@
 **  work differently than '*', and to fix the character-class code.
 */
 
-#include "rsync.h"
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "wildmatch.h"
+
+typedef unsigned char uchar;
 
 /* What character marks an inverted character class? */
 #define NEGATE_CLASS	'!'
@@ -53,10 +59,6 @@
 #define ISUPPER(c) (ISASCII(c) && isupper(c))
 #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
 
-#ifdef WILD_TEST_ITERATIONS
-int wildmatch_iteration_count;
-#endif
-
 /* Match pattern "p" against the a virtually-joined string consisting
  * of "text" and any strings in array "a". */
 static int dowild(const uchar *p, const uchar *text,
@@ -64,10 +66,6 @@ static int dowild(const uchar *p, const uchar *text,
 {
     uchar p_ch;
 
-#ifdef WILD_TEST_ITERATIONS
-    wildmatch_iteration_count++;
-#endif
-
     for ( ; (p_ch = *p) != '\0'; text++, p++) {
 	int matched, special;
 	uchar t_ch, prev_ch;
@@ -289,9 +287,6 @@ static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)
 int wildmatch(const char *pattern, const char *text)
 {
     static const uchar *nomore[1]; /* A NULL pointer. */
-#ifdef WILD_TEST_ITERATIONS
-    wildmatch_iteration_count = 0;
-#endif
     return dowild((const uchar*)pattern, (const uchar*)text, nomore, 0) == TRUE;
 }
 
@@ -300,9 +295,6 @@ int iwildmatch(const char *pattern, const char *text)
 {
     static const uchar *nomore[1]; /* A NULL pointer. */
     int ret;
-#ifdef WILD_TEST_ITERATIONS
-    wildmatch_iteration_count = 0;
-#endif
     ret = dowild((const uchar*)pattern, (const uchar*)text, nomore, 1) == TRUE;
     return ret;
 }
@@ -319,10 +311,6 @@ int wildmatch_array(const char *pattern, const char*const *texts, int where)
     const uchar *text;
     int matched;
 
-#ifdef WILD_TEST_ITERATIONS
-    wildmatch_iteration_count = 0;
-#endif
-
     if (where > 0)
 	text = trailing_N_elements(&a, where);
     else
-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch()
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
                   ` (3 preceding siblings ...)
  2012-09-26 11:25 ` [PATCH 4/5] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
@ 2012-09-26 11:25 ` Nguyễn Thái Ngọc Duy
  2012-09-27 18:08 ` [PATCH 0/5] wildmatch series update Junio C Hamano
  5 siblings, 0 replies; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-26 11:25 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/gitignore.txt        |  3 +++
 attr.c                             |  4 +++-
 dir.c                              |  5 ++++-
 t/t0003-attributes.sh              | 17 +++++++++++++++++
 t/t3001-ls-files-others-exclude.sh | 11 +++++++++++
 5 files changed, 38 insertions(+), 2 deletions(-)

diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index c1f692a..eb81d31 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -93,6 +93,9 @@ PATTERN FORMAT
    For example, "Documentation/{asterisk}.html" matches
    "Documentation/git.html" but not "Documentation/ppc/ppc.html"
    or "tools/perf/Documentation/perf.html".
++
+Contrary to fnmatch(3), git matches "**" to anything including
+slashes, similar to rsync(1).
 
  - A leading slash matches the beginning of the pathname.
    For example, "/{asterisk}.c" matches "cat-file.c" but not
diff --git a/attr.c b/attr.c
index b52efb5..15ebaa1 100644
--- a/attr.c
+++ b/attr.c
@@ -12,6 +12,7 @@
 #include "exec_cmd.h"
 #include "attr.h"
 #include "dir.h"
+#include "wildmatch.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -663,7 +664,8 @@ static int path_matches(const char *pathname, int pathlen,
 		return 0;
 	if (baselen != 0)
 		baselen++;
-	return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+	return (ignore_case && iwildmatch(pattern, pathname + baselen)) ||
+		(!ignore_case && wildmatch(pattern, pathname + baselen));
 }
 
 static int macroexpand_one(int attr_nr, int rem);
diff --git a/dir.c b/dir.c
index 240bf0c..92cda82 100644
--- a/dir.c
+++ b/dir.c
@@ -8,6 +8,7 @@
 #include "cache.h"
 #include "dir.h"
 #include "refs.h"
+#include "wildmatch.h"
 
 struct path_simplify {
 	int len;
@@ -573,7 +574,9 @@ int excluded_from_list(const char *pathname,
 			namelen -= prefix;
 		}
 
-		if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))
+		if (!namelen ||
+		    ((ignore_case && iwildmatch(exclude, name)) ||
+		     (!ignore_case && wildmatch(exclude, name))))
 			return to_exclude;
 	}
 	return -1; /* undecided */
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index febc45c..6c3c554 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -232,4 +232,21 @@ test_expect_success 'bare repository: test info/attributes' '
 	attr_check subdir/a/i unspecified
 '
 
+test_expect_success '"**" test' '
+	cd .. &&
+	echo "**/f foo=bar" >.gitattributes &&
+	cat <<\EOF >expect &&
+f: foo: unspecified
+a/f: foo: bar
+a/b/f: foo: bar
+a/b/c/f: foo: bar
+EOF
+	git check-attr foo -- "f" >actual 2>err &&
+	git check-attr foo -- "a/f" >>actual 2>>err &&
+	git check-attr foo -- "a/b/f" >>actual 2>>err &&
+	git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
+	test_cmp expect actual &&
+	test_line_count = 0 err
+'
+
 test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index c8fe978..67c8bcf 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -214,4 +214,15 @@ test_expect_success 'subdirectory ignore (l1)' '
 	test_cmp expect actual
 '
 
+
+test_expect_success 'ls-files with "**" patterns' '
+	cat <<\EOF >expect &&
+one/a.1
+one/two/a.1
+three/a.1
+EOF
+	git ls-files -o -i --exclude "**/a.1" >actual
+	test_cmp expect actual
+'
+
 test_done
-- 
1.7.12.1.406.g6ab07c4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/5] wildmatch series update
  2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
                   ` (4 preceding siblings ...)
  2012-09-26 11:25 ` [PATCH 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
@ 2012-09-27 18:08 ` Junio C Hamano
  5 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2012-09-27 18:08 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Thanks; will re-queue.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/5] Import wildmatch from rsync
  2012-09-26 11:25 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
@ 2012-10-02 14:07   ` Ævar Arnfjörð Bjarmason
       [not found]     ` <CAHSx_SsnSAV7SVLRnAFvS7AmdRgPkPX3NEM+6HTjfW5r8hXeig@mail.gmail.com>
  0 siblings, 1 reply; 11+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2012-10-02 14:07 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git, Junio C Hamano, Wayne Davison

On Wed, Sep 26, 2012 at 1:25 PM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> These files are from rsync.git commit
> f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit
> before rsync turned GPL-3. All files are imported as-is and
> no-op. Adaptation is done in a separate patch.

Perhaps Wayne Davison (added to CC) wouldn't mind giving us permission
to use the subsequent changes to these files under the GPLv2?

rsync.git $ git --no-pager log --pretty="%h %an %s\n" --reverse
f92f5b166e3019db42bc7fe1aa2f1a9178cd215d.. -- '*wild*'
4fd842f Wayne Davison Switching to GPL 3.\n
8e41b68 Wayne Davison Tweaking the license text a bit more.\n
d3d07a5 Wayne Davison Include 2008 in the copyright years.\n
adc2476 Wayne Davison Output numbers in 3-digit groups by default
(e.g. 1,234,567). Also improved the human-readable output functions,
including adding the ability to output negative numbers.\n
b3bf9b9 Wayne Davison Update the copyright year.\n
fd91c3b Wayne Davison Fix two unused-variable compiler warnings.\n


>
> rsync.git           ->  git.git
> lib/wildmatch.[ch]      wildmatch.[ch]
> wildtest.c              test-wildmatch.c
> wildtest.txt            t/t3070/wildtest.txt
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  t/t3070/wildtest.txt | 165 +++++++++++++++++++++++
>  test-wildmatch.c     | 222 +++++++++++++++++++++++++++++++
>  wildmatch.c          | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  wildmatch.h          |   6 +
>  4 files changed, 761 insertions(+)
>  create mode 100644 t/t3070/wildtest.txt
>  create mode 100644 test-wildmatch.c
>  create mode 100644 wildmatch.c
>  create mode 100644 wildmatch.h
>
> diff --git a/t/t3070/wildtest.txt b/t/t3070/wildtest.txt
> new file mode 100644
> index 0000000..42c1678
> --- /dev/null
> +++ b/t/t3070/wildtest.txt
> @@ -0,0 +1,165 @@
> +# Input is in the following format (all items white-space separated):
> +#
> +# The first two items are 1 or 0 indicating if the wildmat call is expected to
> +# succeed and if fnmatch works the same way as wildmat, respectively.  After
> +# that is a text string for the match, and a pattern string.  Strings can be
> +# quoted (if desired) in either double or single quotes, as well as backticks.
> +#
> +# MATCH FNMATCH_SAME "text to match" 'pattern to use'
> +
> +# Basic wildmat features
> +1 1 foo                        foo
> +0 1 foo                        bar
> +1 1 ''                 ""
> +1 1 foo                        ???
> +0 1 foo                        ??
> +1 1 foo                        *
> +1 1 foo                        f*
> +0 1 foo                        *f
> +1 1 foo                        *foo*
> +1 1 foobar             *ob*a*r*
> +1 1 aaaaaaabababab     *ab
> +1 1 foo*               foo\*
> +0 1 foobar             foo\*bar
> +1 1 f\oo               f\\oo
> +1 1 ball               *[al]?
> +0 1 ten                        [ten]
> +1 1 ten                        **[!te]
> +0 1 ten                        **[!ten]
> +1 1 ten                        t[a-g]n
> +0 1 ten                        t[!a-g]n
> +1 1 ton                        t[!a-g]n
> +1 1 ton                        t[^a-g]n
> +1 1 a]b                        a[]]b
> +1 1 a-b                        a[]-]b
> +1 1 a]b                        a[]-]b
> +0 1 aab                        a[]-]b
> +1 1 aab                        a[]a-]b
> +1 1 ]                  ]
> +
> +# Extended slash-matching features
> +0 1 foo/baz/bar                foo*bar
> +1 1 foo/baz/bar                foo**bar
> +0 1 foo/bar            foo?bar
> +0 1 foo/bar            foo[/]bar
> +0 1 foo/bar            f[^eiu][^eiu][^eiu][^eiu][^eiu]r
> +1 1 foo-bar            f[^eiu][^eiu][^eiu][^eiu][^eiu]r
> +0 1 foo                        **/foo
> +1 1 /foo               **/foo
> +1 1 bar/baz/foo                **/foo
> +0 1 bar/baz/foo                */foo
> +0 0 foo/bar/baz                **/bar*
> +1 1 deep/foo/bar/baz   **/bar/*
> +0 1 deep/foo/bar/baz/  **/bar/*
> +1 1 deep/foo/bar/baz/  **/bar/**
> +0 1 deep/foo/bar       **/bar/*
> +1 1 deep/foo/bar/      **/bar/**
> +1 1 foo/bar/baz                **/bar**
> +1 1 foo/bar/baz/x      */bar/**
> +0 0 deep/foo/bar/baz/x */bar/**
> +1 1 deep/foo/bar/baz/x **/bar/*/*
> +
> +# Various additional tests
> +0 1 acrt               a[c-c]st
> +1 1 acrt               a[c-c]rt
> +0 1 ]                  [!]-]
> +1 1 a                  [!]-]
> +0 1 ''                 \
> +0 1 \                  \
> +0 1 /\                 */\
> +1 1 /\                 */\\
> +1 1 foo                        foo
> +1 1 @foo               @foo
> +0 1 foo                        @foo
> +1 1 [ab]               \[ab]
> +1 1 [ab]               [[]ab]
> +1 1 [ab]               [[:]ab]
> +0 1 [ab]               [[::]ab]
> +1 1 [ab]               [[:digit]ab]
> +1 1 [ab]               [\[:]ab]
> +1 1 ?a?b               \??\?b
> +1 1 abc                        \a\b\c
> +0 1 foo                        ''
> +1 1 foo/bar/baz/to     **/t[o]
> +
> +# Character class tests
> +1 1 a1B                [[:alpha:]][[:digit:]][[:upper:]]
> +0 1 a          [[:digit:][:upper:][:space:]]
> +1 1 A          [[:digit:][:upper:][:space:]]
> +1 1 1          [[:digit:][:upper:][:space:]]
> +0 1 1          [[:digit:][:upper:][:spaci:]]
> +1 1 ' '                [[:digit:][:upper:][:space:]]
> +0 1 .          [[:digit:][:upper:][:space:]]
> +1 1 .          [[:digit:][:punct:][:space:]]
> +1 1 5          [[:xdigit:]]
> +1 1 f          [[:xdigit:]]
> +1 1 D          [[:xdigit:]]
> +1 1 _          [[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
> +#1 1 …         [^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
> +1 1            [^[:alnum:][:alpha:][:blank:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
> +1 1 .          [^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]
> +1 1 5          [a-c[:digit:]x-z]
> +1 1 b          [a-c[:digit:]x-z]
> +1 1 y          [a-c[:digit:]x-z]
> +0 1 q          [a-c[:digit:]x-z]
> +
> +# Additional tests, including some malformed wildmats
> +1 1 ]          [\\-^]
> +0 1 [          [\\-^]
> +1 1 -          [\-_]
> +1 1 ]          [\]]
> +0 1 \]         [\]]
> +0 1 \          [\]]
> +0 1 ab         a[]b
> +0 1 a[]b       a[]b
> +0 1 ab[                ab[
> +0 1 ab         [!
> +0 1 ab         [-
> +1 1 -          [-]
> +0 1 -          [a-
> +0 1 -          [!a-
> +1 1 -          [--A]
> +1 1 5          [--A]
> +1 1 ' '                '[ --]'
> +1 1 $          '[ --]'
> +1 1 -          '[ --]'
> +0 1 0          '[ --]'
> +1 1 -          [---]
> +1 1 -          [------]
> +0 1 j          [a-e-n]
> +1 1 -          [a-e-n]
> +1 1 a          [!------]
> +0 1 [          []-a]
> +1 1 ^          []-a]
> +0 1 ^          [!]-a]
> +1 1 [          [!]-a]
> +1 1 ^          [a^bc]
> +1 1 -b]                [a-]b]
> +0 1 \          [\]
> +1 1 \          [\\]
> +0 1 \          [!\\]
> +1 1 G          [A-\\]
> +0 1 aaabbb     b*a
> +0 1 aabcaa     *ba*
> +1 1 ,          [,]
> +1 1 ,          [\\,]
> +1 1 \          [\\,]
> +1 1 -          [,-.]
> +0 1 +          [,-.]
> +0 1 -.]                [,-.]
> +1 1 2          [\1-\3]
> +1 1 3          [\1-\3]
> +0 1 4          [\1-\3]
> +1 1 \          [[-\]]
> +1 1 [          [[-\]]
> +1 1 ]          [[-\]]
> +0 1 -          [[-\]]
> +
> +# Test recursion and the abort code (use "wildtest -i" to see iteration counts)
> +1 1 -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1  -*-*-*-*-*-*-12-*-*-*-m-*-*-*
> +0 1 -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1  -*-*-*-*-*-*-12-*-*-*-m-*-*-*
> +0 1 -adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1  -*-*-*-*-*-*-12-*-*-*-m-*-*-*
> +1 1 /adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1  /*/*/*/*/*/*/12/*/*/*/m/*/*/*
> +0 1 /adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1  /*/*/*/*/*/*/12/*/*/*/m/*/*/*
> +1 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt              **/*a*b*g*n*t
> +0 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz             **/*a*b*g*n*t
> diff --git a/test-wildmatch.c b/test-wildmatch.c
> new file mode 100644
> index 0000000..88585c2
> --- /dev/null
> +++ b/test-wildmatch.c
> @@ -0,0 +1,222 @@
> +/*
> + * Test suite for the wildmatch code.
> + *
> + * Copyright (C) 2003-2009 Wayne Davison
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, visit the http://fsf.org website.
> + */
> +
> +/*#define COMPARE_WITH_FNMATCH*/
> +
> +#define WILD_TEST_ITERATIONS
> +#include "lib/wildmatch.c"
> +
> +#include <popt.h>
> +
> +#ifdef COMPARE_WITH_FNMATCH
> +#include <fnmatch.h>
> +
> +int fnmatch_errors = 0;
> +#endif
> +
> +int wildmatch_errors = 0;
> +char number_separator = ',';
> +
> +typedef char bool;
> +
> +int output_iterations = 0;
> +int explode_mod = 0;
> +int empties_mod = 0;
> +int empty_at_start = 0;
> +int empty_at_end = 0;
> +
> +static struct poptOption long_options[] = {
> +  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
> +  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
> +  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
> +  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
> +  {0,0,0,0, 0, 0, 0}
> +};
> +
> +/* match just at the start of string (anchored tests) */
> +static void
> +run_test(int line, bool matches,
> +#ifdef COMPARE_WITH_FNMATCH
> +        bool same_as_fnmatch,
> +#endif
> +        const char *text, const char *pattern)
> +{
> +    bool matched;
> +#ifdef COMPARE_WITH_FNMATCH
> +    bool fn_matched;
> +    int flags = strstr(pattern, "**")? 0 : FNM_PATHNAME;
> +#endif
> +
> +    if (explode_mod) {
> +       char buf[MAXPATHLEN*2], *texts[MAXPATHLEN];
> +       int pos = 0, cnt = 0, ndx = 0, len = strlen(text);
> +
> +       if (empty_at_start)
> +           texts[ndx++] = "";
> +       /* An empty string must turn into at least one empty array item. */
> +       while (1) {
> +           texts[ndx] = buf + ndx * (explode_mod + 1);
> +           strlcpy(texts[ndx++], text + pos, explode_mod + 1);
> +           if (pos + explode_mod >= len)
> +               break;
> +           pos += explode_mod;
> +           if (!(++cnt % empties_mod))
> +               texts[ndx++] = "";
> +       }
> +       if (empty_at_end)
> +           texts[ndx++] = "";
> +       texts[ndx] = NULL;
> +       matched = wildmatch_array(pattern, (const char**)texts, 0);
> +    } else
> +       matched = wildmatch(pattern, text);
> +#ifdef COMPARE_WITH_FNMATCH
> +    fn_matched = !fnmatch(pattern, text, flags);
> +#endif
> +    if (matched != matches) {
> +       printf("wildmatch failure on line %d:\n  %s\n  %s\n  expected %s match\n",
> +              line, text, pattern, matches? "a" : "NO");
> +       wildmatch_errors++;
> +    }
> +#ifdef COMPARE_WITH_FNMATCH
> +    if (fn_matched != (matches ^ !same_as_fnmatch)) {
> +       printf("fnmatch disagreement on line %d:\n  %s\n  %s\n  expected %s match\n",
> +              line, text, pattern, matches ^ !same_as_fnmatch? "a" : "NO");
> +       fnmatch_errors++;
> +    }
> +#endif
> +    if (output_iterations) {
> +       printf("%d: \"%s\" iterations = %d\n", line, pattern,
> +              wildmatch_iteration_count);
> +    }
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +    char buf[2048], *s, *string[2], *end[2];
> +    const char *arg;
> +    FILE *fp;
> +    int opt, line, i, flag[2];
> +    poptContext pc = poptGetContext("wildtest", argc, (const char**)argv,
> +                                   long_options, 0);
> +
> +    while ((opt = poptGetNextOpt(pc)) != -1) {
> +       switch (opt) {
> +         case 'e':
> +           arg = poptGetOptArg(pc);
> +           empties_mod = atoi(arg);
> +           if (strchr(arg, 's'))
> +               empty_at_start = 1;
> +           if (strchr(arg, 'e'))
> +               empty_at_end = 1;
> +           if (!explode_mod)
> +               explode_mod = 1024;
> +           break;
> +         default:
> +           fprintf(stderr, "%s: %s\n",
> +                   poptBadOption(pc, POPT_BADOPTION_NOALIAS),
> +                   poptStrerror(opt));
> +           exit(1);
> +       }
> +    }
> +
> +    if (explode_mod && !empties_mod)
> +       empties_mod = 1024;
> +
> +    argv = (char**)poptGetArgs(pc);
> +    if (!argv || argv[1]) {
> +       fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n");
> +       exit(1);
> +    }
> +
> +    if ((fp = fopen(*argv, "r")) == NULL) {
> +       fprintf(stderr, "Unable to open %s\n", *argv);
> +       exit(1);
> +    }
> +
> +    line = 0;
> +    while (fgets(buf, sizeof buf, fp)) {
> +       line++;
> +       if (*buf == '#' || *buf == '\n')
> +           continue;
> +       for (s = buf, i = 0; i <= 1; i++) {
> +           if (*s == '1')
> +               flag[i] = 1;
> +           else if (*s == '0')
> +               flag[i] = 0;
> +           else
> +               flag[i] = -1;
> +           if (*++s != ' ' && *s != '\t')
> +               flag[i] = -1;
> +           if (flag[i] < 0) {
> +               fprintf(stderr, "Invalid flag syntax on line %d of %s:\n%s",
> +                       line, *argv, buf);
> +               exit(1);
> +           }
> +           while (*++s == ' ' || *s == '\t') {}
> +       }
> +       for (i = 0; i <= 1; i++) {
> +           if (*s == '\'' || *s == '"' || *s == '`') {
> +               char quote = *s++;
> +               string[i] = s;
> +               while (*s && *s != quote) s++;
> +               if (!*s) {
> +                   fprintf(stderr, "Unmatched quote on line %d of %s:\n%s",
> +                           line, *argv, buf);
> +                   exit(1);
> +               }
> +               end[i] = s;
> +           }
> +           else {
> +               if (!*s || *s == '\n') {
> +                   fprintf(stderr, "Not enough strings on line %d of %s:\n%s",
> +                           line, *argv, buf);
> +                   exit(1);
> +               }
> +               string[i] = s;
> +               while (*++s && *s != ' ' && *s != '\t' && *s != '\n') {}
> +               end[i] = s;
> +           }
> +           while (*++s == ' ' || *s == '\t') {}
> +       }
> +       *end[0] = *end[1] = '\0';
> +       run_test(line, flag[0],
> +#ifdef COMPARE_WITH_FNMATCH
> +                flag[1],
> +#endif
> +                string[0], string[1]);
> +    }
> +
> +    if (!wildmatch_errors)
> +       fputs("No", stdout);
> +    else
> +       printf("%d", wildmatch_errors);
> +    printf(" wildmatch error%s found.\n", wildmatch_errors == 1? "" : "s");
> +
> +#ifdef COMPARE_WITH_FNMATCH
> +    if (!fnmatch_errors)
> +       fputs("No", stdout);
> +    else
> +       printf("%d", fnmatch_errors);
> +    printf(" fnmatch error%s found.\n", fnmatch_errors == 1? "" : "s");
> +
> +#endif
> +
> +    return 0;
> +}
> diff --git a/wildmatch.c b/wildmatch.c
> new file mode 100644
> index 0000000..f3a1731
> --- /dev/null
> +++ b/wildmatch.c
> @@ -0,0 +1,368 @@
> +/*
> +**  Do shell-style pattern matching for ?, \, [], and * characters.
> +**  It is 8bit clean.
> +**
> +**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
> +**  Rich $alz is now <rsalz@bbn.com>.
> +**
> +**  Modified by Wayne Davison to special-case '/' matching, to make '**'
> +**  work differently than '*', and to fix the character-class code.
> +*/
> +
> +#include "rsync.h"
> +
> +/* What character marks an inverted character class? */
> +#define NEGATE_CLASS   '!'
> +#define NEGATE_CLASS2  '^'
> +
> +#define FALSE 0
> +#define TRUE 1
> +#define ABORT_ALL -1
> +#define ABORT_TO_STARSTAR -2
> +
> +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
> +                                   && *(class) == *(litmatch) \
> +                                   && strncmp((char*)class, litmatch, len) == 0)
> +
> +#if defined STDC_HEADERS || !defined isascii
> +# define ISASCII(c) 1
> +#else
> +# define ISASCII(c) isascii(c)
> +#endif
> +
> +#ifdef isblank
> +# define ISBLANK(c) (ISASCII(c) && isblank(c))
> +#else
> +# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
> +#endif
> +
> +#ifdef isgraph
> +# define ISGRAPH(c) (ISASCII(c) && isgraph(c))
> +#else
> +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))
> +#endif
> +
> +#define ISPRINT(c) (ISASCII(c) && isprint(c))
> +#define ISDIGIT(c) (ISASCII(c) && isdigit(c))
> +#define ISALNUM(c) (ISASCII(c) && isalnum(c))
> +#define ISALPHA(c) (ISASCII(c) && isalpha(c))
> +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))
> +#define ISLOWER(c) (ISASCII(c) && islower(c))
> +#define ISPUNCT(c) (ISASCII(c) && ispunct(c))
> +#define ISSPACE(c) (ISASCII(c) && isspace(c))
> +#define ISUPPER(c) (ISASCII(c) && isupper(c))
> +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
> +
> +#ifdef WILD_TEST_ITERATIONS
> +int wildmatch_iteration_count;
> +#endif
> +
> +static int force_lower_case = 0;
> +
> +/* Match pattern "p" against the a virtually-joined string consisting
> + * of "text" and any strings in array "a". */
> +static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
> +{
> +    uchar p_ch;
> +
> +#ifdef WILD_TEST_ITERATIONS
> +    wildmatch_iteration_count++;
> +#endif
> +
> +    for ( ; (p_ch = *p) != '\0'; text++, p++) {
> +       int matched, special;
> +       uchar t_ch, prev_ch;
> +       while ((t_ch = *text) == '\0') {
> +           if (*a == NULL) {
> +               if (p_ch != '*')
> +                   return ABORT_ALL;
> +               break;
> +           }
> +           text = *a++;
> +       }
> +       if (force_lower_case && ISUPPER(t_ch))
> +           t_ch = tolower(t_ch);
> +       switch (p_ch) {
> +         case '\\':
> +           /* Literal match with following character.  Note that the test
> +            * in "default" handles the p[1] == '\0' failure case. */
> +           p_ch = *++p;
> +           /* FALLTHROUGH */
> +         default:
> +           if (t_ch != p_ch)
> +               return FALSE;
> +           continue;
> +         case '?':
> +           /* Match anything but '/'. */
> +           if (t_ch == '/')
> +               return FALSE;
> +           continue;
> +         case '*':
> +           if (*++p == '*') {
> +               while (*++p == '*') {}
> +               special = TRUE;
> +           } else
> +               special = FALSE;
> +           if (*p == '\0') {
> +               /* Trailing "**" matches everything.  Trailing "*" matches
> +                * only if there are no more slash characters. */
> +               if (!special) {
> +                   do {
> +                       if (strchr((char*)text, '/') != NULL)
> +                           return FALSE;
> +                   } while ((text = *a++) != NULL);
> +               }
> +               return TRUE;
> +           }
> +           while (1) {
> +               if (t_ch == '\0') {
> +                   if ((text = *a++) == NULL)
> +                       break;
> +                   t_ch = *text;
> +                   continue;
> +               }
> +               if ((matched = dowild(p, text, a)) != FALSE) {
> +                   if (!special || matched != ABORT_TO_STARSTAR)
> +                       return matched;
> +               } else if (!special && t_ch == '/')
> +                   return ABORT_TO_STARSTAR;
> +               t_ch = *++text;
> +           }
> +           return ABORT_ALL;
> +         case '[':
> +           p_ch = *++p;
> +#ifdef NEGATE_CLASS2
> +           if (p_ch == NEGATE_CLASS2)
> +               p_ch = NEGATE_CLASS;
> +#endif
> +           /* Assign literal TRUE/FALSE because of "matched" comparison. */
> +           special = p_ch == NEGATE_CLASS? TRUE : FALSE;
> +           if (special) {
> +               /* Inverted character class. */
> +               p_ch = *++p;
> +           }
> +           prev_ch = 0;
> +           matched = FALSE;
> +           do {
> +               if (!p_ch)
> +                   return ABORT_ALL;
> +               if (p_ch == '\\') {
> +                   p_ch = *++p;
> +                   if (!p_ch)
> +                       return ABORT_ALL;
> +                   if (t_ch == p_ch)
> +                       matched = TRUE;
> +               } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
> +                   p_ch = *++p;
> +                   if (p_ch == '\\') {
> +                       p_ch = *++p;
> +                       if (!p_ch)
> +                           return ABORT_ALL;
> +                   }
> +                   if (t_ch <= p_ch && t_ch >= prev_ch)
> +                       matched = TRUE;
> +                   p_ch = 0; /* This makes "prev_ch" get set to 0. */
> +               } else if (p_ch == '[' && p[1] == ':') {
> +                   const uchar *s;
> +                   int i;
> +                   for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
> +                   if (!p_ch)
> +                       return ABORT_ALL;
> +                   i = p - s - 1;
> +                   if (i < 0 || p[-1] != ':') {
> +                       /* Didn't find ":]", so treat like a normal set. */
> +                       p = s - 2;
> +                       p_ch = '[';
> +                       if (t_ch == p_ch)
> +                           matched = TRUE;
> +                       continue;
> +                   }
> +                   if (CC_EQ(s,i, "alnum")) {
> +                       if (ISALNUM(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "alpha")) {
> +                       if (ISALPHA(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "blank")) {
> +                       if (ISBLANK(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "cntrl")) {
> +                       if (ISCNTRL(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "digit")) {
> +                       if (ISDIGIT(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "graph")) {
> +                       if (ISGRAPH(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "lower")) {
> +                       if (ISLOWER(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "print")) {
> +                       if (ISPRINT(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "punct")) {
> +                       if (ISPUNCT(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "space")) {
> +                       if (ISSPACE(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "upper")) {
> +                       if (ISUPPER(t_ch))
> +                           matched = TRUE;
> +                   } else if (CC_EQ(s,i, "xdigit")) {
> +                       if (ISXDIGIT(t_ch))
> +                           matched = TRUE;
> +                   } else /* malformed [:class:] string */
> +                       return ABORT_ALL;
> +                   p_ch = 0; /* This makes "prev_ch" get set to 0. */
> +               } else if (t_ch == p_ch)
> +                   matched = TRUE;
> +           } while (prev_ch = p_ch, (p_ch = *++p) != ']');
> +           if (matched == special || t_ch == '/')
> +               return FALSE;
> +           continue;
> +       }
> +    }
> +
> +    do {
> +       if (*text)
> +           return FALSE;
> +    } while ((text = *a++) != NULL);
> +
> +    return TRUE;
> +}
> +
> +/* Match literal string "s" against the a virtually-joined string consisting
> + * of "text" and any strings in array "a". */
> +static int doliteral(const uchar *s, const uchar *text, const uchar*const *a)
> +{
> +    for ( ; *s != '\0'; text++, s++) {
> +       while (*text == '\0') {
> +           if ((text = *a++) == NULL)
> +               return FALSE;
> +       }
> +       if (*text != *s)
> +           return FALSE;
> +    }
> +
> +    do {
> +       if (*text)
> +           return FALSE;
> +    } while ((text = *a++) != NULL);
> +
> +    return TRUE;
> +}
> +
> +/* Return the last "count" path elements from the concatenated string.
> + * We return a string pointer to the start of the string, and update the
> + * array pointer-pointer to point to any remaining string elements. */
> +static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)
> +{
> +    const uchar*const *a = *a_ptr;
> +    const uchar*const *first_a = a;
> +
> +    while (*a)
> +           a++;
> +
> +    while (a != first_a) {
> +       const uchar *s = *--a;
> +       s += strlen((char*)s);
> +       while (--s >= *a) {
> +           if (*s == '/' && !--count) {
> +               *a_ptr = a+1;
> +               return s+1;
> +           }
> +       }
> +    }
> +
> +    if (count == 1) {
> +       *a_ptr = a+1;
> +       return *a;
> +    }
> +
> +    return NULL;
> +}
> +
> +/* Match the "pattern" against the "text" string. */
> +int wildmatch(const char *pattern, const char *text)
> +{
> +    static const uchar *nomore[1]; /* A NULL pointer. */
> +#ifdef WILD_TEST_ITERATIONS
> +    wildmatch_iteration_count = 0;
> +#endif
> +    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
> +}
> +
> +/* Match the "pattern" against the forced-to-lower-case "text" string. */
> +int iwildmatch(const char *pattern, const char *text)
> +{
> +    static const uchar *nomore[1]; /* A NULL pointer. */
> +    int ret;
> +#ifdef WILD_TEST_ITERATIONS
> +    wildmatch_iteration_count = 0;
> +#endif
> +    force_lower_case = 1;
> +    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
> +    force_lower_case = 0;
> +    return ret;
> +}
> +
> +/* Match pattern "p" against the a virtually-joined string consisting
> + * of all the pointers in array "texts" (which has a NULL pointer at the
> + * end).  The int "where" can be 0 (normal matching), > 0 (match only
> + * the trailing N slash-separated filename components of "texts"), or < 0
> + * (match the "pattern" at the start or after any slash in "texts"). */
> +int wildmatch_array(const char *pattern, const char*const *texts, int where)
> +{
> +    const uchar *p = (const uchar*)pattern;
> +    const uchar*const *a = (const uchar*const*)texts;
> +    const uchar *text;
> +    int matched;
> +
> +#ifdef WILD_TEST_ITERATIONS
> +    wildmatch_iteration_count = 0;
> +#endif
> +
> +    if (where > 0)
> +       text = trailing_N_elements(&a, where);
> +    else
> +       text = *a++;
> +    if (!text)
> +       return FALSE;
> +
> +    if ((matched = dowild(p, text, a)) != TRUE && where < 0
> +     && matched != ABORT_ALL) {
> +       while (1) {
> +           if (*text == '\0') {
> +               if ((text = (uchar*)*a++) == NULL)
> +                   return FALSE;
> +               continue;
> +           }
> +           if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE
> +            && matched != ABORT_TO_STARSTAR)
> +               break;
> +       }
> +    }
> +    return matched == TRUE;
> +}
> +
> +/* Match literal string "s" against the a virtually-joined string consisting
> + * of all the pointers in array "texts" (which has a NULL pointer at the
> + * end).  The int "where" can be 0 (normal matching), or > 0 (match
> + * only the trailing N slash-separated filename components of "texts"). */
> +int litmatch_array(const char *string, const char*const *texts, int where)
> +{
> +    const uchar *s = (const uchar*)string;
> +    const uchar*const *a = (const uchar* const*)texts;
> +    const uchar *text;
> +
> +    if (where > 0)
> +       text = trailing_N_elements(&a, where);
> +    else
> +       text = *a++;
> +    if (!text)
> +       return FALSE;
> +
> +    return doliteral(s, text, a) == TRUE;
> +}
> diff --git a/wildmatch.h b/wildmatch.h
> new file mode 100644
> index 0000000..e7f1a35
> --- /dev/null
> +++ b/wildmatch.h
> @@ -0,0 +1,6 @@
> +/* wildmatch.h */
> +
> +int wildmatch(const char *pattern, const char *text);
> +int iwildmatch(const char *pattern, const char *text);
> +int wildmatch_array(const char *pattern, const char*const *texts, int where);
> +int litmatch_array(const char *string, const char*const *texts, int where);
> --
> 1.7.12.1.406.g6ab07c4
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/5] Import wildmatch from rsync
       [not found]     ` <CAHSx_SsnSAV7SVLRnAFvS7AmdRgPkPX3NEM+6HTjfW5r8hXeig@mail.gmail.com>
@ 2012-10-02 18:26       ` Junio C Hamano
  0 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2012-10-02 18:26 UTC (permalink / raw)
  To: Wayne Davison
  Cc: Ævar Arnfjörð Bjarmason,
	Nguyễn Thái Ngọc Duy, git

Wayne Davison <wayned@samba.org> writes:

> On Tue, Oct 2, 2012 at 7:07 AM, Ævar Arnfjörð Bjarmason <avarab@gmail.com>wrote:
>
>> Perhaps Wayne Davison (added to CC) wouldn't mind giving us permission to
>> use the subsequent changes to these files under the GPLv2?
>
> There have been no changes in the wildmat files since the GPL v3
> changeover.  If there were (and I made them), I'd have been happy to let
> you have them under GPL v2.

Super ;-)

Thanks.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/5] Import wildmatch from rsync
  2012-09-15 12:02 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
@ 2012-09-16  6:49   ` Junio C Hamano
  0 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2012-09-16  6:49 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> These files are from rsync.git commit
> d51a3adb4fca3e6b1b046c6e570828f3bca8fe36. The commit is GPL-3. However
> wildmatch.[ch] have not changed since rsync turned to GPL-3.

I'd like see the last version of rsync that was GPLv2 hunted down
and the copy from that version lifted from it.  If you _know_ they
are identical, then you should be able to do that, no?

>  compat/wildmatch.c             | 368 +++++++++++++++++++++++++++++++++++++++++
>  compat/wildmatch.h             |   6 +

As this will be linked on all platforms, it shouldn't be in compat/
directory.  Unlike xdiff/ that has many files, this can live at the
top-level, just like kwset we borrowed from GNU grep does.

> diff --git a/compat/wildmatch.c b/compat/wildmatch.c
> new file mode 100644
> index 0000000..f3a1731
> --- /dev/null
> +++ b/compat/wildmatch.c
> @@ -0,0 +1,368 @@
> +/*
> +**  Do shell-style pattern matching for ?, \, [], and * characters.
> +**  It is 8bit clean.
> +**
> +**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
> +**  Rich $alz is now <rsalz@bbn.com>.
> +**
> +**  Modified by Wayne Davison to special-case '/' matching, to make '**'
> +**  work differently than '*', and to fix the character-class code.
> +*/
> +
> +#include "rsync.h"

Huh???

Ahh.  The approach you took is "This patch imports the pristine copy
to make it easier to verify the provenance, and the plan is to make
it usable with separate follow-up patches.", I agree that is a very
sensible thing to do.

But please say so in the commit log message.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/5] Import wildmatch from rsync
  2012-09-15 12:01 [PATCH 0/5] Support matching "**" in .gitattributes and .gitignore Nguyễn Thái Ngọc Duy
@ 2012-09-15 12:02 ` Nguyễn Thái Ngọc Duy
  2012-09-16  6:49   ` Junio C Hamano
  0 siblings, 1 reply; 11+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-09-15 12:02 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=UTF-8, Size: 22884 bytes --]

These files are from rsync.git commit
d51a3adb4fca3e6b1b046c6e570828f3bca8fe36. The commit is GPL-3. However
wildmatch.[ch] have not changed since rsync turned to GPL-3.

rsync.git           ->  git.git
lib/wildmatch.[ch]      compat/wildmatch.[ch]
wildtest.c              test-wildmatch.c
wildtest.txt            t/t3070-wildmatch/wildtest.txt

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 compat/wildmatch.c             | 368 +++++++++++++++++++++++++++++++++++++++++
 compat/wildmatch.h             |   6 +
 t/t3070-wildmatch/wildtest.txt | 165 ++++++++++++++++++
 test-wildmatch.c               | 222 +++++++++++++++++++++++++
 4 files changed, 761 insertions(+)
 create mode 100644 compat/wildmatch.c
 create mode 100644 compat/wildmatch.h
 create mode 100644 t/t3070-wildmatch/wildtest.txt
 create mode 100644 test-wildmatch.c

diff --git a/compat/wildmatch.c b/compat/wildmatch.c
new file mode 100644
index 0000000..f3a1731
--- /dev/null
+++ b/compat/wildmatch.c
@@ -0,0 +1,368 @@
+/*
+**  Do shell-style pattern matching for ?, \, [], and * characters.
+**  It is 8bit clean.
+**
+**  Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+**  Rich $alz is now <rsalz@bbn.com>.
+**
+**  Modified by Wayne Davison to special-case '/' matching, to make '**'
+**  work differently than '*', and to fix the character-class code.
+*/
+
+#include "rsync.h"
+
+/* What character marks an inverted character class? */
+#define NEGATE_CLASS	'!'
+#define NEGATE_CLASS2	'^'
+
+#define FALSE 0
+#define TRUE 1
+#define ABORT_ALL -1
+#define ABORT_TO_STARSTAR -2
+
+#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
+				    && *(class) == *(litmatch) \
+				    && strncmp((char*)class, litmatch, len) == 0)
+
+#if defined STDC_HEADERS || !defined isascii
+# define ISASCII(c) 1
+#else
+# define ISASCII(c) isascii(c)
+#endif
+
+#ifdef isblank
+# define ISBLANK(c) (ISASCII(c) && isblank(c))
+#else
+# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifdef isgraph
+# define ISGRAPH(c) (ISASCII(c) && isgraph(c))
+#else
+# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))
+#endif
+
+#define ISPRINT(c) (ISASCII(c) && isprint(c))
+#define ISDIGIT(c) (ISASCII(c) && isdigit(c))
+#define ISALNUM(c) (ISASCII(c) && isalnum(c))
+#define ISALPHA(c) (ISASCII(c) && isalpha(c))
+#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))
+#define ISLOWER(c) (ISASCII(c) && islower(c))
+#define ISPUNCT(c) (ISASCII(c) && ispunct(c))
+#define ISSPACE(c) (ISASCII(c) && isspace(c))
+#define ISUPPER(c) (ISASCII(c) && isupper(c))
+#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
+
+#ifdef WILD_TEST_ITERATIONS
+int wildmatch_iteration_count;
+#endif
+
+static int force_lower_case = 0;
+
+/* Match pattern "p" against the a virtually-joined string consisting
+ * of "text" and any strings in array "a". */
+static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
+{
+    uchar p_ch;
+
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count++;
+#endif
+
+    for ( ; (p_ch = *p) != '\0'; text++, p++) {
+	int matched, special;
+	uchar t_ch, prev_ch;
+	while ((t_ch = *text) == '\0') {
+	    if (*a == NULL) {
+		if (p_ch != '*')
+		    return ABORT_ALL;
+		break;
+	    }
+	    text = *a++;
+	}
+	if (force_lower_case && ISUPPER(t_ch))
+	    t_ch = tolower(t_ch);
+	switch (p_ch) {
+	  case '\\':
+	    /* Literal match with following character.  Note that the test
+	     * in "default" handles the p[1] == '\0' failure case. */
+	    p_ch = *++p;
+	    /* FALLTHROUGH */
+	  default:
+	    if (t_ch != p_ch)
+		return FALSE;
+	    continue;
+	  case '?':
+	    /* Match anything but '/'. */
+	    if (t_ch == '/')
+		return FALSE;
+	    continue;
+	  case '*':
+	    if (*++p == '*') {
+		while (*++p == '*') {}
+		special = TRUE;
+	    } else
+		special = FALSE;
+	    if (*p == '\0') {
+		/* Trailing "**" matches everything.  Trailing "*" matches
+		 * only if there are no more slash characters. */
+		if (!special) {
+		    do {
+			if (strchr((char*)text, '/') != NULL)
+			    return FALSE;
+		    } while ((text = *a++) != NULL);
+		}
+		return TRUE;
+	    }
+	    while (1) {
+		if (t_ch == '\0') {
+		    if ((text = *a++) == NULL)
+			break;
+		    t_ch = *text;
+		    continue;
+		}
+		if ((matched = dowild(p, text, a)) != FALSE) {
+		    if (!special || matched != ABORT_TO_STARSTAR)
+			return matched;
+		} else if (!special && t_ch == '/')
+		    return ABORT_TO_STARSTAR;
+		t_ch = *++text;
+	    }
+	    return ABORT_ALL;
+	  case '[':
+	    p_ch = *++p;
+#ifdef NEGATE_CLASS2
+	    if (p_ch == NEGATE_CLASS2)
+		p_ch = NEGATE_CLASS;
+#endif
+	    /* Assign literal TRUE/FALSE because of "matched" comparison. */
+	    special = p_ch == NEGATE_CLASS? TRUE : FALSE;
+	    if (special) {
+		/* Inverted character class. */
+		p_ch = *++p;
+	    }
+	    prev_ch = 0;
+	    matched = FALSE;
+	    do {
+		if (!p_ch)
+		    return ABORT_ALL;
+		if (p_ch == '\\') {
+		    p_ch = *++p;
+		    if (!p_ch)
+			return ABORT_ALL;
+		    if (t_ch == p_ch)
+			matched = TRUE;
+		} else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
+		    p_ch = *++p;
+		    if (p_ch == '\\') {
+			p_ch = *++p;
+			if (!p_ch)
+			    return ABORT_ALL;
+		    }
+		    if (t_ch <= p_ch && t_ch >= prev_ch)
+			matched = TRUE;
+		    p_ch = 0; /* This makes "prev_ch" get set to 0. */
+		} else if (p_ch == '[' && p[1] == ':') {
+		    const uchar *s;
+		    int i;
+		    for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
+		    if (!p_ch)
+			return ABORT_ALL;
+		    i = p - s - 1;
+		    if (i < 0 || p[-1] != ':') {
+			/* Didn't find ":]", so treat like a normal set. */
+			p = s - 2;
+			p_ch = '[';
+			if (t_ch == p_ch)
+			    matched = TRUE;
+			continue;
+		    }
+		    if (CC_EQ(s,i, "alnum")) {
+			if (ISALNUM(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "alpha")) {
+			if (ISALPHA(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "blank")) {
+			if (ISBLANK(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "cntrl")) {
+			if (ISCNTRL(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "digit")) {
+			if (ISDIGIT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "graph")) {
+			if (ISGRAPH(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "lower")) {
+			if (ISLOWER(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "print")) {
+			if (ISPRINT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "punct")) {
+			if (ISPUNCT(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "space")) {
+			if (ISSPACE(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "upper")) {
+			if (ISUPPER(t_ch))
+			    matched = TRUE;
+		    } else if (CC_EQ(s,i, "xdigit")) {
+			if (ISXDIGIT(t_ch))
+			    matched = TRUE;
+		    } else /* malformed [:class:] string */
+			return ABORT_ALL;
+		    p_ch = 0; /* This makes "prev_ch" get set to 0. */
+		} else if (t_ch == p_ch)
+		    matched = TRUE;
+	    } while (prev_ch = p_ch, (p_ch = *++p) != ']');
+	    if (matched == special || t_ch == '/')
+		return FALSE;
+	    continue;
+	}
+    }
+
+    do {
+	if (*text)
+	    return FALSE;
+    } while ((text = *a++) != NULL);
+
+    return TRUE;
+}
+
+/* Match literal string "s" against the a virtually-joined string consisting
+ * of "text" and any strings in array "a". */
+static int doliteral(const uchar *s, const uchar *text, const uchar*const *a)
+{
+    for ( ; *s != '\0'; text++, s++) {
+	while (*text == '\0') {
+	    if ((text = *a++) == NULL)
+		return FALSE;
+	}
+	if (*text != *s)
+	    return FALSE;
+    }
+
+    do {
+	if (*text)
+	    return FALSE;
+    } while ((text = *a++) != NULL);
+
+    return TRUE;
+}
+
+/* Return the last "count" path elements from the concatenated string.
+ * We return a string pointer to the start of the string, and update the
+ * array pointer-pointer to point to any remaining string elements. */
+static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count)
+{
+    const uchar*const *a = *a_ptr;
+    const uchar*const *first_a = a;
+
+    while (*a)
+	    a++;
+
+    while (a != first_a) {
+	const uchar *s = *--a;
+	s += strlen((char*)s);
+	while (--s >= *a) {
+	    if (*s == '/' && !--count) {
+		*a_ptr = a+1;
+		return s+1;
+	    }
+	}
+    }
+
+    if (count == 1) {
+	*a_ptr = a+1;
+	return *a;
+    }
+
+    return NULL;
+}
+
+/* Match the "pattern" against the "text" string. */
+int wildmatch(const char *pattern, const char *text)
+{
+    static const uchar *nomore[1]; /* A NULL pointer. */
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+    return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
+}
+
+/* Match the "pattern" against the forced-to-lower-case "text" string. */
+int iwildmatch(const char *pattern, const char *text)
+{
+    static const uchar *nomore[1]; /* A NULL pointer. */
+    int ret;
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+    force_lower_case = 1;
+    ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE;
+    force_lower_case = 0;
+    return ret;
+}
+
+/* Match pattern "p" against the a virtually-joined string consisting
+ * of all the pointers in array "texts" (which has a NULL pointer at the
+ * end).  The int "where" can be 0 (normal matching), > 0 (match only
+ * the trailing N slash-separated filename components of "texts"), or < 0
+ * (match the "pattern" at the start or after any slash in "texts"). */
+int wildmatch_array(const char *pattern, const char*const *texts, int where)
+{
+    const uchar *p = (const uchar*)pattern;
+    const uchar*const *a = (const uchar*const*)texts;
+    const uchar *text;
+    int matched;
+
+#ifdef WILD_TEST_ITERATIONS
+    wildmatch_iteration_count = 0;
+#endif
+
+    if (where > 0)
+	text = trailing_N_elements(&a, where);
+    else
+	text = *a++;
+    if (!text)
+	return FALSE;
+
+    if ((matched = dowild(p, text, a)) != TRUE && where < 0
+     && matched != ABORT_ALL) {
+	while (1) {
+	    if (*text == '\0') {
+		if ((text = (uchar*)*a++) == NULL)
+		    return FALSE;
+		continue;
+	    }
+	    if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE
+	     && matched != ABORT_TO_STARSTAR)
+		break;
+	}
+    }
+    return matched == TRUE;
+}
+
+/* Match literal string "s" against the a virtually-joined string consisting
+ * of all the pointers in array "texts" (which has a NULL pointer at the
+ * end).  The int "where" can be 0 (normal matching), or > 0 (match
+ * only the trailing N slash-separated filename components of "texts"). */
+int litmatch_array(const char *string, const char*const *texts, int where)
+{
+    const uchar *s = (const uchar*)string;
+    const uchar*const *a = (const uchar* const*)texts;
+    const uchar *text;
+
+    if (where > 0)
+	text = trailing_N_elements(&a, where);
+    else
+	text = *a++;
+    if (!text)
+	return FALSE;
+
+    return doliteral(s, text, a) == TRUE;
+}
diff --git a/compat/wildmatch.h b/compat/wildmatch.h
new file mode 100644
index 0000000..e7f1a35
--- /dev/null
+++ b/compat/wildmatch.h
@@ -0,0 +1,6 @@
+/* wildmatch.h */
+
+int wildmatch(const char *pattern, const char *text);
+int iwildmatch(const char *pattern, const char *text);
+int wildmatch_array(const char *pattern, const char*const *texts, int where);
+int litmatch_array(const char *string, const char*const *texts, int where);
diff --git a/t/t3070-wildmatch/wildtest.txt b/t/t3070-wildmatch/wildtest.txt
new file mode 100644
index 0000000..42c1678
--- /dev/null
+++ b/t/t3070-wildmatch/wildtest.txt
@@ -0,0 +1,165 @@
+# Input is in the following format (all items white-space separated):
+#
+# The first two items are 1 or 0 indicating if the wildmat call is expected to
+# succeed and if fnmatch works the same way as wildmat, respectively.  After
+# that is a text string for the match, and a pattern string.  Strings can be
+# quoted (if desired) in either double or single quotes, as well as backticks.
+#
+# MATCH FNMATCH_SAME "text to match" 'pattern to use'
+
+# Basic wildmat features
+1 1 foo			foo
+0 1 foo			bar
+1 1 ''			""
+1 1 foo			???
+0 1 foo			??
+1 1 foo			*
+1 1 foo			f*
+0 1 foo			*f
+1 1 foo			*foo*
+1 1 foobar		*ob*a*r*
+1 1 aaaaaaabababab	*ab
+1 1 foo*		foo\*
+0 1 foobar		foo\*bar
+1 1 f\oo		f\\oo
+1 1 ball		*[al]?
+0 1 ten			[ten]
+1 1 ten			**[!te]
+0 1 ten			**[!ten]
+1 1 ten			t[a-g]n
+0 1 ten			t[!a-g]n
+1 1 ton			t[!a-g]n
+1 1 ton			t[^a-g]n
+1 1 a]b			a[]]b
+1 1 a-b			a[]-]b
+1 1 a]b			a[]-]b
+0 1 aab			a[]-]b
+1 1 aab			a[]a-]b
+1 1 ]			]
+
+# Extended slash-matching features
+0 1 foo/baz/bar		foo*bar
+1 1 foo/baz/bar		foo**bar
+0 1 foo/bar		foo?bar
+0 1 foo/bar		foo[/]bar
+0 1 foo/bar		f[^eiu][^eiu][^eiu][^eiu][^eiu]r
+1 1 foo-bar		f[^eiu][^eiu][^eiu][^eiu][^eiu]r
+0 1 foo			**/foo
+1 1 /foo		**/foo
+1 1 bar/baz/foo		**/foo
+0 1 bar/baz/foo		*/foo
+0 0 foo/bar/baz		**/bar*
+1 1 deep/foo/bar/baz	**/bar/*
+0 1 deep/foo/bar/baz/	**/bar/*
+1 1 deep/foo/bar/baz/	**/bar/**
+0 1 deep/foo/bar	**/bar/*
+1 1 deep/foo/bar/	**/bar/**
+1 1 foo/bar/baz		**/bar**
+1 1 foo/bar/baz/x	*/bar/**
+0 0 deep/foo/bar/baz/x	*/bar/**
+1 1 deep/foo/bar/baz/x	**/bar/*/*
+
+# Various additional tests
+0 1 acrt		a[c-c]st
+1 1 acrt		a[c-c]rt
+0 1 ]			[!]-]
+1 1 a			[!]-]
+0 1 ''			\
+0 1 \			\
+0 1 /\			*/\
+1 1 /\			*/\\
+1 1 foo			foo
+1 1 @foo		@foo
+0 1 foo			@foo
+1 1 [ab]		\[ab]
+1 1 [ab]		[[]ab]
+1 1 [ab]		[[:]ab]
+0 1 [ab]		[[::]ab]
+1 1 [ab]		[[:digit]ab]
+1 1 [ab]		[\[:]ab]
+1 1 ?a?b		\??\?b
+1 1 abc			\a\b\c
+0 1 foo			''
+1 1 foo/bar/baz/to	**/t[o]
+
+# Character class tests
+1 1 a1B		[[:alpha:]][[:digit:]][[:upper:]]
+0 1 a		[[:digit:][:upper:][:space:]]
+1 1 A		[[:digit:][:upper:][:space:]]
+1 1 1		[[:digit:][:upper:][:space:]]
+0 1 1		[[:digit:][:upper:][:spaci:]]
+1 1 ' '		[[:digit:][:upper:][:space:]]
+0 1 .		[[:digit:][:upper:][:space:]]
+1 1 .		[[:digit:][:punct:][:space:]]
+1 1 5		[[:xdigit:]]
+1 1 f		[[:xdigit:]]
+1 1 D		[[:xdigit:]]
+1 1 _		[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+#1 1 …		[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+1 1 \x7f		[^[:alnum:][:alpha:][:blank:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]
+1 1 .		[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]
+1 1 5		[a-c[:digit:]x-z]
+1 1 b		[a-c[:digit:]x-z]
+1 1 y		[a-c[:digit:]x-z]
+0 1 q		[a-c[:digit:]x-z]
+
+# Additional tests, including some malformed wildmats
+1 1 ]		[\\-^]
+0 1 [		[\\-^]
+1 1 -		[\-_]
+1 1 ]		[\]]
+0 1 \]		[\]]
+0 1 \		[\]]
+0 1 ab		a[]b
+0 1 a[]b	a[]b
+0 1 ab[		ab[
+0 1 ab		[!
+0 1 ab		[-
+1 1 -		[-]
+0 1 -		[a-
+0 1 -		[!a-
+1 1 -		[--A]
+1 1 5		[--A]
+1 1 ' '		'[ --]'
+1 1 $		'[ --]'
+1 1 -		'[ --]'
+0 1 0		'[ --]'
+1 1 -		[---]
+1 1 -		[------]
+0 1 j		[a-e-n]
+1 1 -		[a-e-n]
+1 1 a		[!------]
+0 1 [		[]-a]
+1 1 ^		[]-a]
+0 1 ^		[!]-a]
+1 1 [		[!]-a]
+1 1 ^		[a^bc]
+1 1 -b]		[a-]b]
+0 1 \		[\]
+1 1 \		[\\]
+0 1 \		[!\\]
+1 1 G		[A-\\]
+0 1 aaabbb	b*a
+0 1 aabcaa	*ba*
+1 1 ,		[,]
+1 1 ,		[\\,]
+1 1 \		[\\,]
+1 1 -		[,-.]
+0 1 +		[,-.]
+0 1 -.]		[,-.]
+1 1 2		[\1-\3]
+1 1 3		[\1-\3]
+0 1 4		[\1-\3]
+1 1 \		[[-\]]
+1 1 [		[[-\]]
+1 1 ]		[[-\]]
+0 1 -		[[-\]]
+
+# Test recursion and the abort code (use "wildtest -i" to see iteration counts)
+1 1 -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+0 1 -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+0 1 -adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1	-*-*-*-*-*-*-12-*-*-*-m-*-*-*
+1 1 /adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1	/*/*/*/*/*/*/12/*/*/*/m/*/*/*
+0 1 /adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1	/*/*/*/*/*/*/12/*/*/*/m/*/*/*
+1 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt		**/*a*b*g*n*t
+0 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz		**/*a*b*g*n*t
diff --git a/test-wildmatch.c b/test-wildmatch.c
new file mode 100644
index 0000000..88585c2
--- /dev/null
+++ b/test-wildmatch.c
@@ -0,0 +1,222 @@
+/*
+ * Test suite for the wildmatch code.
+ *
+ * Copyright (C) 2003-2009 Wayne Davison
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, visit the http://fsf.org website.
+ */
+
+/*#define COMPARE_WITH_FNMATCH*/
+
+#define WILD_TEST_ITERATIONS
+#include "lib/wildmatch.c"
+
+#include <popt.h>
+
+#ifdef COMPARE_WITH_FNMATCH
+#include <fnmatch.h>
+
+int fnmatch_errors = 0;
+#endif
+
+int wildmatch_errors = 0;
+char number_separator = ',';
+
+typedef char bool;
+
+int output_iterations = 0;
+int explode_mod = 0;
+int empties_mod = 0;
+int empty_at_start = 0;
+int empty_at_end = 0;
+
+static struct poptOption long_options[] = {
+  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
+  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
+  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
+  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
+  {0,0,0,0, 0, 0, 0}
+};
+
+/* match just at the start of string (anchored tests) */
+static void
+run_test(int line, bool matches,
+#ifdef COMPARE_WITH_FNMATCH
+	 bool same_as_fnmatch,
+#endif
+	 const char *text, const char *pattern)
+{
+    bool matched;
+#ifdef COMPARE_WITH_FNMATCH
+    bool fn_matched;
+    int flags = strstr(pattern, "**")? 0 : FNM_PATHNAME;
+#endif
+
+    if (explode_mod) {
+	char buf[MAXPATHLEN*2], *texts[MAXPATHLEN];
+	int pos = 0, cnt = 0, ndx = 0, len = strlen(text);
+
+	if (empty_at_start)
+	    texts[ndx++] = "";
+	/* An empty string must turn into at least one empty array item. */
+	while (1) {
+	    texts[ndx] = buf + ndx * (explode_mod + 1);
+	    strlcpy(texts[ndx++], text + pos, explode_mod + 1);
+	    if (pos + explode_mod >= len)
+		break;
+	    pos += explode_mod;
+	    if (!(++cnt % empties_mod))
+		texts[ndx++] = "";
+	}
+	if (empty_at_end)
+	    texts[ndx++] = "";
+	texts[ndx] = NULL;
+	matched = wildmatch_array(pattern, (const char**)texts, 0);
+    } else
+	matched = wildmatch(pattern, text);
+#ifdef COMPARE_WITH_FNMATCH
+    fn_matched = !fnmatch(pattern, text, flags);
+#endif
+    if (matched != matches) {
+	printf("wildmatch failure on line %d:\n  %s\n  %s\n  expected %s match\n",
+	       line, text, pattern, matches? "a" : "NO");
+	wildmatch_errors++;
+    }
+#ifdef COMPARE_WITH_FNMATCH
+    if (fn_matched != (matches ^ !same_as_fnmatch)) {
+	printf("fnmatch disagreement on line %d:\n  %s\n  %s\n  expected %s match\n",
+	       line, text, pattern, matches ^ !same_as_fnmatch? "a" : "NO");
+	fnmatch_errors++;
+    }
+#endif
+    if (output_iterations) {
+	printf("%d: \"%s\" iterations = %d\n", line, pattern,
+	       wildmatch_iteration_count);
+    }
+}
+
+int
+main(int argc, char **argv)
+{
+    char buf[2048], *s, *string[2], *end[2];
+    const char *arg;
+    FILE *fp;
+    int opt, line, i, flag[2];
+    poptContext pc = poptGetContext("wildtest", argc, (const char**)argv,
+				    long_options, 0);
+
+    while ((opt = poptGetNextOpt(pc)) != -1) {
+	switch (opt) {
+	  case 'e':
+	    arg = poptGetOptArg(pc);
+	    empties_mod = atoi(arg);
+	    if (strchr(arg, 's'))
+		empty_at_start = 1;
+	    if (strchr(arg, 'e'))
+		empty_at_end = 1;
+	    if (!explode_mod)
+		explode_mod = 1024;
+	    break;
+	  default:
+	    fprintf(stderr, "%s: %s\n",
+		    poptBadOption(pc, POPT_BADOPTION_NOALIAS),
+		    poptStrerror(opt));
+	    exit(1);
+	}
+    }
+
+    if (explode_mod && !empties_mod)
+	empties_mod = 1024;
+
+    argv = (char**)poptGetArgs(pc);
+    if (!argv || argv[1]) {
+	fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n");
+	exit(1);
+    }
+
+    if ((fp = fopen(*argv, "r")) == NULL) {
+	fprintf(stderr, "Unable to open %s\n", *argv);
+	exit(1);
+    }
+
+    line = 0;
+    while (fgets(buf, sizeof buf, fp)) {
+	line++;
+	if (*buf == '#' || *buf == '\n')
+	    continue;
+	for (s = buf, i = 0; i <= 1; i++) {
+	    if (*s == '1')
+		flag[i] = 1;
+	    else if (*s == '0')
+		flag[i] = 0;
+	    else
+		flag[i] = -1;
+	    if (*++s != ' ' && *s != '\t')
+		flag[i] = -1;
+	    if (flag[i] < 0) {
+		fprintf(stderr, "Invalid flag syntax on line %d of %s:\n%s",
+			line, *argv, buf);
+		exit(1);
+	    }
+	    while (*++s == ' ' || *s == '\t') {}
+	}
+	for (i = 0; i <= 1; i++) {
+	    if (*s == '\'' || *s == '"' || *s == '`') {
+		char quote = *s++;
+		string[i] = s;
+		while (*s && *s != quote) s++;
+		if (!*s) {
+		    fprintf(stderr, "Unmatched quote on line %d of %s:\n%s",
+			    line, *argv, buf);
+		    exit(1);
+		}
+		end[i] = s;
+	    }
+	    else {
+		if (!*s || *s == '\n') {
+		    fprintf(stderr, "Not enough strings on line %d of %s:\n%s",
+			    line, *argv, buf);
+		    exit(1);
+		}
+		string[i] = s;
+		while (*++s && *s != ' ' && *s != '\t' && *s != '\n') {}
+		end[i] = s;
+	    }
+	    while (*++s == ' ' || *s == '\t') {}
+	}
+	*end[0] = *end[1] = '\0';
+	run_test(line, flag[0],
+#ifdef COMPARE_WITH_FNMATCH
+		 flag[1],
+#endif
+		 string[0], string[1]);
+    }
+
+    if (!wildmatch_errors)
+	fputs("No", stdout);
+    else
+	printf("%d", wildmatch_errors);
+    printf(" wildmatch error%s found.\n", wildmatch_errors == 1? "" : "s");
+
+#ifdef COMPARE_WITH_FNMATCH
+    if (!fnmatch_errors)
+	fputs("No", stdout);
+    else
+	printf("%d", fnmatch_errors);
+    printf(" fnmatch error%s found.\n", fnmatch_errors == 1? "" : "s");
+
+#endif
+
+    return 0;
+}
-- 
1.7.12.403.gce5cf6f.dirty

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2012-10-02 18:26 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-09-26 11:25 [PATCH 0/5] wildmatch series update Nguyễn Thái Ngọc Duy
2012-09-26 11:25 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
2012-10-02 14:07   ` Ævar Arnfjörð Bjarmason
     [not found]     ` <CAHSx_SsnSAV7SVLRnAFvS7AmdRgPkPX3NEM+6HTjfW5r8hXeig@mail.gmail.com>
2012-10-02 18:26       ` Junio C Hamano
2012-09-26 11:25 ` [PATCH 2/5] compat/wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
2012-09-26 11:25 ` [PATCH 3/5] compat/wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
2012-09-26 11:25 ` [PATCH 4/5] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
2012-09-26 11:25 ` [PATCH 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
2012-09-27 18:08 ` [PATCH 0/5] wildmatch series update Junio C Hamano
  -- strict thread matches above, loose matches on Subject: below --
2012-09-15 12:01 [PATCH 0/5] Support matching "**" in .gitattributes and .gitignore Nguyễn Thái Ngọc Duy
2012-09-15 12:02 ` [PATCH 1/5] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
2012-09-16  6:49   ` Junio C Hamano

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).