threads / patch / 31539

patch, 5 partsSupport matching "**" in .gitattributes and .gitignore

Subject: [PATCH 0/5] Support matching "**" in .gitattributes and .gitignore

## tl;dr

34 messages between Sep 15, 2012 and Sep 25, 2012. Diffs are folded; open one to read it.

replies: 33people: 7as markdown or json

Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:01 UTC · lore

A while back I posted a proof of concept using wildmatch() from rsync. Back then the obstacle was case-insensitive matching support. I did not realize that there was iwildmatch() that does exactly that.

So here again a series that is probably ready for consumption. Now patterns that contain slashes will always go through (i)wildmatch(). fnmatch() is only used for basename matching.

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()
 Documentation/gitignore.txt    |   3 +
 Makefile                       |   6 +
 attr.c                         |   4 +-
 compat/wildmatch.c             | 373 +++++++++++++++++++++++++++++++++++++++++
 compat/wildmatch.h             |   6 +
 dir.c                          |   5 +-
 t/t3070-wildmatch.sh           |  27 +++
 t/t3070-wildmatch/wildtest.txt | 165 ++++++++++++++++++
 test-wildmatch.c               | 228 +++++++++++++++++++++++++
 9 files changed, 815 insertions(+), 2 deletions(-)
 create mode 100644 compat/wildmatch.c
 create mode 100644 compat/wildmatch.h
 create mode 100755 t/t3070-wildmatch.sh
 create mode 100644 t/t3070-wildmatch/wildtest.txt
 create mode 100644 test-wildmatch.c
-- 
1.7.12.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 1/5] Import wildmatch from rsync

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
Show changes to 4 files +761 −0

compat/wildmatch.c, compat/wildmatch.h, t/t3070-wildmatch/wildtest.txt, 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 		[^[: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
Junio C Hamano· Sep 16, 2012, 06:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 1/5] Import wildmatch from rsync

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.

Show 18 quoted lines
> 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.
Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 2/5] compat/wildmatch: remove static variable force_lower_case

One place less to worry about thread safety
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 compat/wildmatch.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)
Show changes to compat/wildmatch.c +7 −10
diff --git a/compat/wildmatch.c b/compat/wildmatch.c
index f3a1731..e824eb2 100644
--- a/compat/wildmatch.c
+++ b/compat/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.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 3/5] compat/wildmatch: fix case-insensitive matching

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>
---
 compat/wildmatch.c | 2 ++
 1 file changed, 2 insertions(+)
Show changes to compat/wildmatch.c +2 −0
diff --git a/compat/wildmatch.c b/compat/wildmatch.c
index e824eb2..c7f7f9f 100644
--- a/compat/wildmatch.c
+++ b/compat/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.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 4/5] Integrate wildmatch to git

This makes wildmatch.c part of libgit.a and builds test-wildmatch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Makefile             |  6 ++++++
 compat/wildmatch.c   |  8 +++++++-
 t/t3070-wildmatch.sh | 27 +++++++++++++++++++++++++++
 test-wildmatch.c     |  8 +++++++-
 4 files changed, 47 insertions(+), 2 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh
Show changes to 4 files +47 −2

Makefile, compat/wildmatch.c, t/t3070-wildmatch.sh, test-wildmatch.c

diff --git a/Makefile b/Makefile
index 56301dc..c3608e6 100644
--- a/Makefile
+++ b/Makefile
@@ -511,6 +511,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))
 
@@ -605,6 +606,7 @@ LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
 LIB_H += compat/precompose_utf8.h
 LIB_H += compat/terminal.h
+LIB_H += compat/wildmatch.h
 LIB_H += compat/win32/dirent.h
 LIB_H += compat/win32/poll.h
 LIB_H += compat/win32/pthread.h
@@ -709,6 +711,7 @@ LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
 LIB_OBJS += compat/terminal.o
+LIB_OBJS += compat/wildmatch.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
 LIB_OBJS += connected.o
@@ -2586,6 +2589,9 @@ test-svn-fe$X: vcs-svn/lib.a
 test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
+test-wildmatch$X: test-wildmatch.o GIT-LDFLAGS
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) -lpopt
+
 check-sha1:: test-sha1$X
 	./test-sha1.sh
 
diff --git a/compat/wildmatch.c b/compat/wildmatch.c
index c7f7f9f..625cb0c 100644
--- a/compat/wildmatch.c
+++ b/compat/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	'!'
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
new file mode 100755
index 0000000..7fb63ff
--- /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-wildmatch/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..2c506a0 100644
--- a/test-wildmatch.c
+++ b/test-wildmatch.c
@@ -20,7 +20,13 @@
 /*#define COMPARE_WITH_FNMATCH*/
 
 #define WILD_TEST_ITERATIONS
-#include "lib/wildmatch.c"
+#include "compat/wildmatch.c"
+
+#define MAXPATHLEN 1024
+#ifdef NO_STRLCPY
+#include "compat/strlcpy.c"
+#define strlcpy gitstrlcpy
+#endif
 
 #include <popt.h>
 
-- 
1.7.12.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 15, 2012, 12:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitignore.txt | 3 +++
 attr.c                      | 4 +++-
 dir.c                       | 5 ++++-
 3 files changed, 10 insertions(+), 2 deletions(-)
Show changes to 3 files +10 −2

Documentation/gitignore.txt, attr.c, dir.c

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 3430faf..f06ad95 100644
--- a/attr.c
+++ b/attr.c
@@ -12,6 +12,7 @@
 #include "exec_cmd.h"
 #include "attr.h"
 #include "dir.h"
+#include "compat/wildmatch.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -666,7 +667,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 4868339..c17f9ff 100644
--- a/dir.c
+++ b/dir.c
@@ -8,6 +8,7 @@
 #include "cache.h"
 #include "dir.h"
 #include "refs.h"
+#include "compat/wildmatch.h"
 
 struct path_simplify {
 	int len;
@@ -575,7 +576,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 */
-- 
1.7.12.403.gce5cf6f.dirty
Philip Oakley· Sep 15, 2012, 14:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

How to create the " [PATCH 0/5]" first email?

The git format-patch allows numbered patches to be created, however I'm not sure how folk generate the initial "0/n" patch. Which of the various options should I be using?

Also, being on windows, the various 'mbox' and 'MUA' discussions are new to me, so the format-patch step where :-

"Typically it will be placed in a MUA’s drafts folder, edited to add timely commentary that should not go in the changelog after the three dashes, and then sent as a message whose body, in our example, starts with "arch/arm config files were…". On the receiving end, readers can save interesting patches in a UNIX mailbox and apply them with git-am(1)."

hides a multitude of implicit knowledge steps. Is there an extended description of what that would mean from a platform independent viewpoint? e.g. if the patches are separte files and an mbox is one consolidated file, how to get from one to the other so that 'it' can be sent by 'git send-mail'.

I'm also missing an understanding of the preparation stage where one tries to tidy up the various commit messages becaue they weren't explicit, specfic nor concise enough, so I suspect that there is an implicit `git format-patch` <-> `git am` loop of sharpening the mbox patches before submission to the list that I'm missing. Has this described somewhere?

Philip Oakley
Junio C Hamano· Sep 15, 2012, 17:08 UTC · re: Philip Oakley · lore

Re: How to create the " [PATCH 0/5]" first email?

"Philip Oakley" <philipoakley@iee.org> writes:
Show 12 quoted lines
> "Typically it will be placed in a MUA’s drafts folder, edited to add
> timely commentary that should not go in the changelog after the three
> dashes, and then sent as a message whose body, in our example, starts
> with "arch/arm config files were…". On the receiving end, readers can
> save interesting patches in a UNIX mailbox and apply them with
> git-am(1)."
>
> hides a multitude of implicit knowledge steps. Is there an extended
> description of what that would mean from a platform independent
> viewpoint? e.g. if the patches are separte files and an mbox is one
> consolidated file, how to get from one to the other so that 'it' can be
> sent by 'git send-mail'.

If you plan to use "git send-email" to send the final results out, you should consider "git send-email" as your "MUA" in the quoted paragraph. And that will be very platform independent viewpoint to see things from.

"git format-patch -o my-series/ --cover-letter ..." would treat "my-series/" directory as "MUA's drafts folder" and prepares the messages you would want to send out, and you can proof-read and edit the files in there before telling your "MUA" to send them out, with "git send-email ... my-series/*.patch" or something.

> I'm also missing an understanding of the preparation stage where one
> tries to tidy up the various commit messages becaue they weren't
> explicit, specfic nor concise enough,...

Many people usually do "rebase -i" until perfection and then a single final invocation of "format-patch". Of course, the "final" can and should further be proof-read and it is fine to do typofixes in the format-patch output files without going back to the commits before sending them out.

> ...so I suspect that there is an
> implicit `git format-patch` <-> `git am` loop of sharpening the mbox
> patches before submission to the list that I'm missing. Has this 
> described somewhere?
Philip Oakley· Sep 17, 2012, 22:55 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

From: "Junio C Hamano" <gitster@pobox.com>
Show 21 quoted lines
> "Philip Oakley" <philipoakley@iee.org> writes:
>
>> "Typically it will be placed in a MUA’s drafts folder, edited to add
>> timely commentary that should not go in the changelog after the three
>> dashes, and then sent as a message whose body, in our example, starts
>> with "arch/arm config files were…". On the receiving end, readers 
>> can
>> save interesting patches in a UNIX mailbox and apply them with
>> git-am(1)."
>>
>> hides a multitude of implicit knowledge steps. Is there an extended
>> description of what that would mean from a platform independent
>> viewpoint? e.g. if the patches are separte files and an mbox is one
>> consolidated file, how to get from one to the other so that 'it' can 
>> be
>> sent by 'git send-mail'.
>
> If you plan to use "git send-email" to send the final results out,
> you should consider "git send-email" as your "MUA" in the quoted
> paragraph.  And that will be very platform independent viewpoint to
> see things from.

On git for windows (msysgit)n there were a couple of other steps I had to do https://git.wiki.kernel.org/index.php/MSysGit:UsingSendEmail as Outlooklook Express isn't really a suitable MUA ;-) [noted for other list readers]

Show 7 quoted lines
>
> "git format-patch -o my-series/ --cover-letter ..."  would treat
> "my-series/" directory as "MUA's drafts folder" and prepares the
> messages you would want to send out, and you can proof-read and edit
> the files in there before telling your "MUA" to send them out, with
> "git send-email ... my-series/*.patch" or something.
>

I hadn't picked up from the man page that the --cover-letter would do the [PATCH 0/n] - should it?

Show 9 quoted lines
>> I'm also missing an understanding of the preparation stage where one
>> tries to tidy up the various commit messages becaue they weren't
>> explicit, specfic nor concise enough,...
>
> Many people usually do "rebase -i" until perfection and then a
> single final invocation of "format-patch".  Of course, the "final"
> can and should further be proof-read and it is fine to do typofixes
> in the format-patch output files without going back to the commits
> before sending them out.
I did an initial rebase to correct a few obvious mistakes (e.g. an extra 
file that had crept in), but then, after some false starts, used
    git format-patch pu --stdout > fix_Docs.patch
to get a single file I could inspect and refine for both the commit 
messages and content.

I then applied it (using git am) to a temp branch to see what it produced, and could repeat the cycle until the patches looked right.

Show 6 quoted lines
>
>> ...so I suspect that there is an
>> implicit `git format-patch` <-> `git am` loop of sharpening the mbox
>> patches before submission to the list that I'm missing. Has this
>> described somewhere?
> --

However, when it came to creating the series, with comments, I couldn't see a way of having my comments within my local commits, but preparing a patch series that would properly include the '---' separator.

Is there a way of getting format-patch to change some line break sequence (within the commit message) to the '---' three dashes patch break suitable for submission to the list?

Philip
Junio C Hamano· Sep 17, 2012, 23:49 UTC · re: Philip Oakley · lore

Re: How to create the " [PATCH 0/5]" first email?

"Philip Oakley" <philipoakley@iee.org> writes:
> I then applied it (using git am) to a temp branch to see what it
> produced, and could repeat the cycle until the patches looked right.

That's another obvious and valid way to prepare your series. It all depends on how comfortable you are to directly edit patches. Some people fear it. Some don't. Some can do it with their eyes closed ;-).

> However, when it came to creating the series, with comments, I
> couldn't see a way of having my comments within my local commits, but
> preparing a patch series that would properly include the '---'
> separator.
An unofficial trick that works is to write the
    ---
     * This is an additional comment

yourself when running "git commit". That will be propagated to the output from format-patch. You will have another "---" in front of the diffstat, but nobody is hurt by that.

But when doing a big series that deserves a cover letter [PATCH 0/n], you will use editor on the output from format-patch anyway, and I find it simpler to do the follow-on comments at that point myself.

Personal preferences vary, so whatever makes you feel comfortable with and works well for you is good.

Jeff King· Sep 18, 2012, 00:15 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Mon, Sep 17, 2012 at 04:49:39PM -0700, Junio C Hamano wrote:
Show 15 quoted lines
> > However, when it came to creating the series, with comments, I
> > couldn't see a way of having my comments within my local commits, but
> > preparing a patch series that would properly include the '---'
> > separator.
> 
> An unofficial trick that works is to write the
> 
>     ---
> 
>      * This is an additional comment
> 
> 
> yourself when running "git commit".  That will be propagated to the
> output from format-patch.  You will have another "---" in front of
> the diffstat, but nobody is hurt by that.

But note that using "format-patch -s" will break; it puts the sign-off below the "---".

> But when doing a big series that deserves a cover letter [PATCH 0/n],
> you will use editor on the output from format-patch anyway, and I
> find it simpler to do the follow-on comments at that point myself.

Me too (actually, I load it all into mutt and then comment on each as I send them out, but it amounts to the same thing, seeing as how my MUA just invokes $EDITOR when I edit a mail).

-Peff
Junio C Hamano· Sep 18, 2012, 00:36 UTC · re: Jeff King · lore

Re: How to create the " [PATCH 0/5]" first email?

Jeff King <peff@peff.net> writes:
Show 20 quoted lines
> On Mon, Sep 17, 2012 at 04:49:39PM -0700, Junio C Hamano wrote:
>
>> > However, when it came to creating the series, with comments, I
>> > couldn't see a way of having my comments within my local commits, but
>> > preparing a patch series that would properly include the '---'
>> > separator.
>> 
>> An unofficial trick that works is to write the
>> 
>>     ---
>> 
>>      * This is an additional comment
>> 
>> 
>> yourself when running "git commit".  That will be propagated to the
>> output from format-patch.  You will have another "---" in front of
>> the diffstat, but nobody is hurt by that.
>
> But note that using "format-patch -s" will break; it puts the sign-off
> below the "---".

I think "format-patch -s" is a workflow mistake in the first place. You should be doing the sign-off the commit when you commit in the first place. It is not like "I cannot sign off because I think it is still iffy" or anything.

But your point still stands; "commit -s" will not see through that official trick either ;-).

Show 7 quoted lines
>> But when doing a big series that deserves a cover letter [PATCH 0/n],
>> you will use editor on the output from format-patch anyway, and I
>> find it simpler to do the follow-on comments at that point myself.
>
> Me too (actually, I load it all into mutt and then comment on each as I
> send them out, but it amounts to the same thing, seeing as how my MUA
> just invokes $EDITOR when I edit a mail).
Jeff King· Sep 18, 2012, 18:55 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Mon, Sep 17, 2012 at 05:36:35PM -0700, Junio C Hamano wrote:
Show 7 quoted lines
> > But note that using "format-patch -s" will break; it puts the sign-off
> > below the "---".
> 
> I think "format-patch -s" is a workflow mistake in the first place.
> You should be doing the sign-off the commit when you commit in the
> first place.  It is not like "I cannot sign off because I think it
> is still iffy" or anything.

It is mostly "I do not signoff because typing '-s' each time I commit is a pain". I know it is supposed to be a conscious decision, but I think it is a little silly. Since everything I do for git.git is going to be signed-off, either I will automate it, or I will end up typing it without thinking. Whenever I make a conscious decision, it is to _withhold_ signoff, since that is the exceptional case.

But even without that, I still think format-patch is a reasonable time to do it. It is the time when I proof-read my commit message and patch in its final form, and think "do I really want to send this?". That seems to me like a reasonable time to make such a conscious decision to signoff (or not).

> But your point still stands; "commit -s" will not see through that
> official trick either ;-).
Yes. :)
-Peff
Junio C Hamano· Sep 18, 2012, 19:11 UTC · re: Jeff King · lore

Re: How to create the " [PATCH 0/5]" first email?

Jeff King <peff@peff.net> writes:
> But even without that, I still think format-patch is a reasonable time
> to do it. It is the time when I proof-read my commit message and patch
> in its final form, and think "do I really want to send this?".

But it is not like "I cannot sign off because I think it is still iffy".

Show 7 quoted lines
> seems to me like a reasonable time to make such a conscious decision to
> signoff (or not).
>
>> But your point still stands; "commit -s" will not see through that
>> official trick either ;-).
>
> Yes. :)
Actually, no.  "commit -s" does not have any need to see through it.
	... hack hack hack ...
        $ git commit -a -s
        ... editor opens, you see your Sign-off at the end, with
        ... the cursor sitting on the first line
        ... edit the title, move to the line below the Sign-off,
        ... and do the "---\n\n * comment" thing.

And this survives "rebase -i" (but not "format-patch | am" for obvious reasons).

So I take it back. The time you do the "git commit" for the very first time for this change that may need to be rerolled number of times is the right time to say "-s".

Jeff King· Sep 18, 2012, 19:16 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Tue, Sep 18, 2012 at 12:11:58PM -0700, Junio C Hamano wrote:
Show 8 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > But even without that, I still think format-patch is a reasonable time
> > to do it. It is the time when I proof-read my commit message and patch
> > in its final form, and think "do I really want to send this?".
> 
> But it is not like "I cannot sign off because I think it is still
> iffy".

No, that is not the particular reason in my case, but I think I explained other reasons why "format-patch -s" is not a wrong workflow.

Show 16 quoted lines
> >> But your point still stands; "commit -s" will not see through that
> >> official trick either ;-).
> >
> > Yes. :)
> 
> Actually, no.  "commit -s" does not have any need to see through it.
> 
> 	... hack hack hack ...
>         $ git commit -a -s
>         ... editor opens, you see your Sign-off at the end, with
>         ... the cursor sitting on the first line
>         ... edit the title, move to the line below the Sign-off,
>         ... and do the "---\n\n * comment" thing.
> 
> And this survives "rebase -i" (but not "format-patch | am" for
> obvious reasons).

Yes, if your particular workflow is to signoff the very first time you commit. But it would not work for:

 ... hack hack hack ...
 $ git commit -a
   ... make a note after "---" ...
 ... hack hack hack ...
 ... OK, looks good, ready to signoff ...
 $ git commit --amend -s

So it can work, but it is workflow dependent, and in general is a little flaky with the automagic signoff. You may want to signoff later for a variety of reasons, not the least of which is that you forgot to type "-s" the first time.

> So I take it back.  The time you do the "git commit" for the very
> first time for this change that may need to be rerolled number of
> times is the right time to say "-s".
If you remember to type it. :)
-Peff
Junio C Hamano· Sep 18, 2012, 19:47 UTC · re: Jeff King · lore

Re: How to create the " [PATCH 0/5]" first email?

Jeff King <peff@peff.net> writes:
Show 13 quoted lines
> On Tue, Sep 18, 2012 at 12:11:58PM -0700, Junio C Hamano wrote:
>
>> Jeff King <peff@peff.net> writes:
>> 
>> > But even without that, I still think format-patch is a reasonable time
>> > to do it. It is the time when I proof-read my commit message and patch
>> > in its final form, and think "do I really want to send this?".
>> 
>> But it is not like "I cannot sign off because I think it is still
>> iffy".
>
> No, that is not the particular reason in my case, but I think I
> explained other reasons why "format-patch -s" is not a wrong workflow.

Then I didn't read it. What does "do I really want to send this?" have anything to do with DCO in any case?

Jeff King· Sep 18, 2012, 20:16 UTC · re: Philip Oakley · lore

Re: How to create the " [PATCH 0/5]" first email?

On Tue, Sep 18, 2012 at 09:10:26PM +0100, Philip Oakley wrote:
> From: "Junio C Hamano" <gitster@pobox.com>
> >have anything to do with DCO in any case?
> 
> What's DCO an abbreviation of?
Developer's Certificate of Origin. See SubmittingPatches.
-Peff
Jeff King· Sep 18, 2012, 20:16 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Tue, Sep 18, 2012 at 12:47:36PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> >> Jeff King <peff@peff.net> writes:
> >> 
> >> > But even without that, I still think format-patch is a reasonable time
> >> > to do it. It is the time when I proof-read my commit message and patch
> >> > in its final form, and think "do I really want to send this?".
> >> 
> >> But it is not like "I cannot sign off because I think it is still
> >> iffy".
> >
> > No, that is not the particular reason in my case, but I think I
> > explained other reasons why "format-patch -s" is not a wrong workflow.
> 
> Then I didn't read it.  What does "do I really want to send this?"
> have anything to do with DCO in any case?

Because it is an excellent time to think about "am I willing and able to agree to the DCO?" As I said, for me personally working on git.git, that is not generally an issue. But I think it is perfectly reasonable for somebody to work and commit in isolation, and then only decide on the DCO during the sending phase (perhaps because they need to clear it with their company's legal department or some such). In other words, "it is iffy at the time of commit" might be exactly the reason for some people.

If you are responding to my "that is not the particular reason in my case", I will paraphrase the reason I gave earlier: I find it annoying and pointless to type "-s" on every commit. We do not have commit.signoff, but we do have format.signoff.

-Peff
Wesley J. Landaker· Sep 18, 2012, 20:42 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Monday, September 17, 2012 17:49:39 Junio C Hamano wrote:
Show 23 quoted lines
> "Philip Oakley" <philipoakley@iee.org> writes:
> > I then applied it (using git am) to a temp branch to see what it
> > produced, and could repeat the cycle until the patches looked right.
> 
> That's another obvious and valid way to prepare your series.  It all
> depends on how comfortable you are to directly edit patches.  Some
> people fear it.  Some don't.  Some can do it with their eyes closed ;-).
> 
> > However, when it came to creating the series, with comments, I
> > couldn't see a way of having my comments within my local commits, but
> > preparing a patch series that would properly include the '---'
> > separator.
> 
> An unofficial trick that works is to write the
> 
>     ---
> 
>      * This is an additional comment
> 
> 
> yourself when running "git commit".  That will be propagated to the
> output from format-patch.  You will have another "---" in front of
> the diffstat, but nobody is hurt by that.

One thing I have done is to add the additional comments I want with "git notes", then give the "--notes" option to format-patch or send-email.

Unfortunately, this sticks the notes right into the commit message section, because the "--notes" option is actually a diff option, not something format-patch knows about, so you have to make sure to manually move it.

But even so, I've found it a a nice way to track comments.
Jan Engelhardt· Sep 23, 2012, 12:03 UTC · re: Junio C Hamano · lore

Re: How to create the " [PATCH 0/5]" first email?

On Saturday 2012-09-15 19:08, Junio C Hamano wrote:
Show 11 quoted lines
>
>If you plan to use "git send-email" to send the final results out,
>you should consider "git send-email" as your "MUA" in the quoted
>paragraph.  And that will be very platform independent viewpoint to
>see things from.
>
>"git format-patch -o my-series/ --cover-letter ..."  would treat
>"my-series/" directory as "MUA's drafts folder" and prepares the
>messages you would want to send out, and you can proof-read and edit
>the files in there before telling your "MUA" to send them out, with
>"git send-email ... my-series/*.patch" or something.
One can also send [0/n] with a normal MUA, and then use
 git send-email --in-reply-to '<messageidof0@yourhost.no>' commitrange
It's not like 0/n has to be emitted at the same second 1/n is :)
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 0/5] Support matching "**" in .gitattributes and .gitignore

Changes in v2:
 - Correct commit sha-1 in the 1/5 to the last GPL-2 rsync commit.
   State it is pristine import. Move wildmatch.[ch] to top dir.
 - Add tests to 5/5 (I forgot to test that the integration should run
   well, in addition to wildmatch() working in general)

Note that there might be a regression because wildmatch() does not care about LC_CTYPE when dealing with character classes, while libc's fnmatch might. But it's probably not worth bothering about because we match against path names, which do not necessarily follow LC_TYPE but the file system's encoding.

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                           |   6 +
 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-wildmatch/wildtest.txt     | 165 ++++++++++++++++
 test-wildmatch.c                   | 228 +++++++++++++++++++++++
 wildmatch.c                        | 373 +++++++++++++++++++++++++++++++++++++
 wildmatch.h                        |   6 +
 12 files changed, 844 insertions(+), 2 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh
 create mode 100644 t/t3070-wildmatch/wildtest.txt
 create mode 100644 test-wildmatch.c
 create mode 100644 wildmatch.c
 create mode 100644 wildmatch.h
-- 
1.7.12.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 1/5] Import wildmatch from rsync

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-wildmatch/wildtest.txt

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

t/t3070-wildmatch/wildtest.txt, test-wildmatch.c, wildmatch.c, wildmatch.h

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 		[^[: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.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 2/5] compat/wildmatch: remove static variable force_lower_case

One place less to worry about thread safety
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 wildmatch.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)
Show changes to wildmatch.c +7 −10
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.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 3/5] compat/wildmatch: fix case-insensitive matching

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>
---
 wildmatch.c | 2 ++
 1 file changed, 2 insertions(+)
Show changes to wildmatch.c +2 −0
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.403.gce5cf6f.dirty
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 4/5] Integrate wildmatch to git

This makes wildmatch.c part of libgit.a and builds test-wildmatch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore           |  1 +
 Makefile             |  6 ++++++
 t/t3070-wildmatch.sh | 27 +++++++++++++++++++++++++++
 test-wildmatch.c     |  8 +++++++-
 wildmatch.c          |  8 +++++++-
 5 files changed, 48 insertions(+), 2 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh
Show changes to 5 files +48 −2

.gitignore, Makefile, t/t3070-wildmatch.sh, test-wildmatch.c, wildmatch.c

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 56301dc..745e88c 100644
--- a/Makefile
+++ b/Makefile
@@ -511,6 +511,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))
 
@@ -683,6 +684,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
@@ -814,6 +816,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
@@ -2586,6 +2589,9 @@ test-svn-fe$X: vcs-svn/lib.a
 test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
+test-wildmatch$X: test-wildmatch.o GIT-LDFLAGS
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) -lpopt
+
 check-sha1:: test-sha1$X
 	./test-sha1.sh
 
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
new file mode 100755
index 0000000..7fb63ff
--- /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-wildmatch/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..828188a 100644
--- a/test-wildmatch.c
+++ b/test-wildmatch.c
@@ -20,7 +20,13 @@
 /*#define COMPARE_WITH_FNMATCH*/
 
 #define WILD_TEST_ITERATIONS
-#include "lib/wildmatch.c"
+#include "wildmatch.c"
+
+#define MAXPATHLEN 1024
+#ifdef NO_STRLCPY
+#include "compat/strlcpy.c"
+#define strlcpy gitstrlcpy
+#endif
 
 #include <popt.h>
 
diff --git a/wildmatch.c b/wildmatch.c
index c7f7f9f..625cb0c 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	'!'
-- 
1.7.12.403.gce5cf6f.dirty
Junio C Hamano· Sep 17, 2012, 05:31 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 4/5] Integrate wildmatch to git

Am I missing some includes?

test-wildmatch.c:50: error: array type has incomplete element type test-wildmatch.c:52: error: 'POPT_ARG_NONE' undeclared here (not in a function) test-wildmatch.c:53: error: 'POPT_ARG_STRING' undeclared here (not in a function) test-wildmatch.c:54: error: 'POPT_ARG_INT' undeclared here (not in a function) test-wildmatch.c: In function 'main': test-wildmatch.c:122: error: 'poptContext' undeclared (first use in this function) test-wildmatch.c:122: error: (Each undeclared identifier is reported only once test-wildmatch.c:122: error: for each function it appears in.) test-wildmatch.c:122: error: expected ';' before 'pc' cc1: warnings being treated as errors test-wildmatch.c:125: error: implicit declaration of function 'poptGetNextOpt' test-wildmatch.c:125: error: 'pc' undeclared (first use in this function) test-wildmatch.c:128: error: implicit declaration of function 'poptGetOptArg' test-wildmatch.c:139: error: implicit declaration of function 'poptBadOption' test-wildmatch.c:139: error: 'POPT_BADOPTION_NOALIAS' undeclared (first use in this function) test-wildmatch.c:140: error: implicit declaration of function 'poptStrerror' test-wildmatch.c:148: error: implicit declaration of function 'poptGetArgs'

Junio C Hamano· Sep 17, 2012, 05:54 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 4/5] Integrate wildmatch to git

Junio C Hamano <gitster@pobox.com> writes:
Show 19 quoted lines
> Am I missing some includes?
>
> test-wildmatch.c:50: error: array type has incomplete element type
> test-wildmatch.c:52: error: 'POPT_ARG_NONE' undeclared here (not in a function)
> test-wildmatch.c:53: error: 'POPT_ARG_STRING' undeclared here (not in a function)
> test-wildmatch.c:54: error: 'POPT_ARG_INT' undeclared here (not in a function)
> test-wildmatch.c: In function 'main':
> test-wildmatch.c:122: error: 'poptContext' undeclared (first use in this function)
> test-wildmatch.c:122: error: (Each undeclared identifier is reported only once
> test-wildmatch.c:122: error: for each function it appears in.)
> test-wildmatch.c:122: error: expected ';' before 'pc'
> cc1: warnings being treated as errors
> test-wildmatch.c:125: error: implicit declaration of function 'poptGetNextOpt'
> test-wildmatch.c:125: error: 'pc' undeclared (first use in this function)
> test-wildmatch.c:128: error: implicit declaration of function 'poptGetOptArg'
> test-wildmatch.c:139: error: implicit declaration of function 'poptBadOption'
> test-wildmatch.c:139: error: 'POPT_BADOPTION_NOALIAS' undeclared (first use in this function)
> test-wildmatch.c:140: error: implicit declaration of function 'poptStrerror'
> test-wildmatch.c:148: error: implicit declaration of function 'poptGetArgs'

Yeah, popt.h it is. It is a bit distasteful that we have a build dependency only to build test-* helper on something that we do not even have runtime dependency on.

Nguyen Thai Ngoc Duy· Sep 17, 2012, 05:57 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 4/5] Integrate wildmatch to git

On Mon, Sep 17, 2012 at 12:54 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Yeah, popt.h it is.  It is a bit distasteful that we have a build
> dependency only to build test-* helper on something that we do not
> even have runtime dependency on.

Yep. I don't know how popular libpopt is. But if it's undesired, we could rewrite test-wildmatch.c to use parse-options.c instead. We fork from rsync anyway due to license conflict so we don't have to keep it close to upstream any more.

-- 
Duy
Nguyen Thai Ngoc Duy· Sep 17, 2012, 12:40 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 4/5] Integrate wildmatch to git

On Sun, Sep 16, 2012 at 10:54:57PM -0700, Junio C Hamano wrote:
> Yeah, popt.h it is.  It is a bit distasteful that we have a build
> dependency only to build test-* helper on something that we do not
> even have runtime dependency on.

Perhaps this squash-in? It kills libpopt and removes the "#include "wildmatch.c"". Now we really do test what libgit.a carries.

-- 8< --
Show changes to 3 files +25 −66

Makefile, test-wildmatch.c, wildmatch.c

diff --git a/Makefile b/Makefile
index 745e88c..093ab9c 100644
--- a/Makefile
+++ b/Makefile
@@ -2589,9 +2589,6 @@ test-svn-fe$X: vcs-svn/lib.a
 test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
 
-test-wildmatch$X: test-wildmatch.o GIT-LDFLAGS
-	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) -lpopt
-
 check-sha1:: test-sha1$X
 	./test-sha1.sh
 
diff --git a/test-wildmatch.c b/test-wildmatch.c
index 828188a..b94921b 100644
--- a/test-wildmatch.c
+++ b/test-wildmatch.c
@@ -19,17 +19,18 @@
 
 /*#define COMPARE_WITH_FNMATCH*/
 
-#define WILD_TEST_ITERATIONS
-#include "wildmatch.c"
+#include "cache.h"
+#include "parse-options.h"
+#include "wildmatch.h"
 
+#ifndef MAXPATHLEN
 #define MAXPATHLEN 1024
+#endif
 #ifdef NO_STRLCPY
 #include "compat/strlcpy.c"
 #define strlcpy gitstrlcpy
 #endif
 
-#include <popt.h>
-
 #ifdef COMPARE_WITH_FNMATCH
 #include <fnmatch.h>
 
@@ -41,18 +42,16 @@ 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;
+char *empties;
 
-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 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) */
@@ -106,51 +105,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 625cb0c..f153f8a 100644
--- a/wildmatch.c
+++ b/wildmatch.c
@@ -59,10 +59,6 @@ typedef unsigned char uchar;
 #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,
@@ -70,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;
@@ -295,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;
 }
 
@@ -306,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;
 }
@@ -325,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
-- 8< --
Junio C Hamano· Sep 17, 2012, 17:20 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH v2 4/5] Integrate wildmatch to git

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 7 quoted lines
> On Sun, Sep 16, 2012 at 10:54:57PM -0700, Junio C Hamano wrote:
>> Yeah, popt.h it is.  It is a bit distasteful that we have a build
>> dependency only to build test-* helper on something that we do not
>> even have runtime dependency on.
>
> Perhaps this squash-in? It kills libpopt and removes the "#include
> "wildmatch.c"". Now we really do test what libgit.a carries.
Sounds fine to me.  Thanks; will take a look.
Show 192 quoted lines
>
> -- 8< --
> diff --git a/Makefile b/Makefile
> index 745e88c..093ab9c 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -2589,9 +2589,6 @@ test-svn-fe$X: vcs-svn/lib.a
>  test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
>  	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
>  
> -test-wildmatch$X: test-wildmatch.o GIT-LDFLAGS
> -	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) -lpopt
> -
>  check-sha1:: test-sha1$X
>  	./test-sha1.sh
>  
> diff --git a/test-wildmatch.c b/test-wildmatch.c
> index 828188a..b94921b 100644
> --- a/test-wildmatch.c
> +++ b/test-wildmatch.c
> @@ -19,17 +19,18 @@
>  
>  /*#define COMPARE_WITH_FNMATCH*/
>  
> -#define WILD_TEST_ITERATIONS
> -#include "wildmatch.c"
> +#include "cache.h"
> +#include "parse-options.h"
> +#include "wildmatch.h"
>  
> +#ifndef MAXPATHLEN
>  #define MAXPATHLEN 1024
> +#endif
>  #ifdef NO_STRLCPY
>  #include "compat/strlcpy.c"
>  #define strlcpy gitstrlcpy
>  #endif
>  
> -#include <popt.h>
> -
>  #ifdef COMPARE_WITH_FNMATCH
>  #include <fnmatch.h>
>  
> @@ -41,18 +42,16 @@ 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;
> +char *empties;
>  
> -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 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) */
> @@ -106,51 +105,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 625cb0c..f153f8a 100644
> --- a/wildmatch.c
> +++ b/wildmatch.c
> @@ -59,10 +59,6 @@ typedef unsigned char uchar;
>  #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,
> @@ -70,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;
> @@ -295,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;
>  }
>  
> @@ -306,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;
>  }
> @@ -325,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
> -- 8< --
Nguyễn Thái Ngọc Duy· Sep 16, 2012, 15:27 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 5/5] Support "**" in .gitignore and .gitattributes patterns using wildmatch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.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(-)
Show changes to 5 files +38 −2

Documentation/gitignore.txt, attr.c, dir.c, t/t0003-attributes.sh, t/t3001-ls-files-others-exclude.sh

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 3430faf..2cea18c 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";
@@ -666,7 +667,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 4868339..55ab2b3 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;
@@ -575,7 +576,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.403.gce5cf6f.dirty
Johannes Sixt· Sep 25, 2012, 07:01 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 6/5] side-step a make rule that builds t3070-wildmatch

From: Johannes Sixt <j6t@kdbg.org>
Running "make t3070-wildmatch" aborts with this error:
   $ make t3070-wildmatch
   *** t3070-wildmatch.sh ***
   # passed all 11 test(s)
   1..11
   cat t3070-wildmatch.sh >t3070-wildmatch
   /bin/sh.exe: t3070-wildmatch: File exists
   make: *** [t3070-wildmatch] Error 1

It seems that there is a make rule that attempts to transform a *.sh file into a file without the extension. The rule fails because t3070-wildmatch is a directory. Rename it to t3070.

Of course, nobody would run exactly "make t3070-wildmatch". But it is common (at least for me) to run "make t[3-9]*" or similar, which would include t3070-wildmatch. With this patch, it includes t3070, but that does not hurt because make just skips it with "Nothing to be done for `t3070'."

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 Feel free to squash this patch if you re-roll the series.
 t/t3070-wildmatch.sh                      | 2 +-
 t/{t3070-wildmatch => t3070}/wildtest.txt | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename t/{t3070-wildmatch => t3070}/wildtest.txt (100%)
Show changes to 2 files +1 −1

t/t3070-wildmatch.sh, t/t3070-wildmatch/wildtest.txt

diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
index 7fb63ff..c4da26c 100755
--- a/t/t3070-wildmatch.sh
+++ b/t/t3070-wildmatch.sh
@@ -6,7 +6,7 @@ test_description='wildmatch tests'

 test_wildmatch() {
     test_expect_success "wildmatch $*" "
-	test-wildmatch $* ../t3070-wildmatch/wildtest.txt >actual &&
+	test-wildmatch $* ../t3070/wildtest.txt >actual &&
 	echo 'No wildmatch errors found.' >expected &&
 	test_cmp expected actual
     "
diff --git a/t/t3070-wildmatch/wildtest.txt b/t/t3070/wildtest.txt
similarity index 100%
rename from t/t3070-wildmatch/wildtest.txt
rename to t/t3070/wildtest.txt
-- 
1.7.12.1.1626.gf25df0e

← back to recent threads