git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH 00/10] nd/wildmatch take 2
@ 2012-10-05  4:40 Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 01/10] gitignore: make pattern parsing code a separate function Nguyễn Thái Ngọc Duy
                   ` (9 more replies)
  0 siblings, 10 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:40 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

The first four patches are ignore/attr cleanups. The following imports
wildmatch, nothing new there. The last patch limits allowed syntax to
a safe subset: "abc/**", "**/def" and "abc/**/def".

Nguyễn Thái Ngọc Duy (10):
  gitignore: make pattern parsing code a separate function
  attr: avoid strlen() on every match
  attr: avoid searching for basename on every match
  attr: more matching optimizations from .gitignore
  Import wildmatch from rsync
  wildmatch: remove static variable force_lower_case
  wildmatch: fix case-insensitive matching
  Integrate wildmatch to git
  Support "**" in .gitignore and .gitattributes patterns using
    wildmatch()
  gitignore: forbid "abc**def"

 .gitignore                         |   1 +
 Documentation/gitattributes.txt    |   2 +
 Documentation/gitignore.txt        |   5 +
 Makefile                           |   3 +
 attr.c                             |  89 ++++++++--
 dir.c                              |  82 ++++++---
 dir.h                              |   3 +-
 t/t0003-attributes.sh              |  22 +++
 t/t3001-ls-files-others-exclude.sh |  16 ++
 t/t3070-wildmatch.sh               |  27 +++
 t/t3070/wildtest.txt               | 165 +++++++++++++++++
 test-wildmatch.c                   | 208 ++++++++++++++++++++++
 wildmatch.c                        | 355 +++++++++++++++++++++++++++++++++++++
 wildmatch.h                        |   6 +
 14 files changed, 942 insertions(+), 42 deletions(-)
 create mode 100755 t/t3070-wildmatch.sh
 create mode 100644 t/t3070/wildtest.txt
 create mode 100644 test-wildmatch.c
 create mode 100644 wildmatch.c
 create mode 100644 wildmatch.h

-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 01/10] gitignore: make pattern parsing code a separate function
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 02/10] attr: avoid strlen() on every match Nguyễn Thái Ngọc Duy
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

This function can later be reused by attr.c. Also turn to_exclude
field into a flag.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 If we go with glob->regex conversion way, this is where we could
 rewrite the pattern (and set EXC_FLAG_REGEX).

 dir.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++---------------------
 dir.h |  2 +-
 2 files changed, 50 insertions(+), 23 deletions(-)

diff --git a/dir.c b/dir.c
index 240bf0c..cd13920 100644
--- a/dir.c
+++ b/dir.c
@@ -308,42 +308,69 @@ static int no_wildcard(const char *string)
 	return string[simple_length(string)] == '\0';
 }
 
+static void parse_exclude_pattern(const char **pattern,
+				  int *patternlen,
+				  int *flags,
+				  int *nowildcardlen)
+{
+	const char *p = *pattern;
+	size_t i, len;
+
+	*flags = 0;
+	if (*p == '!') {
+		*flags |= EXC_FLAG_NEGATIVE;
+		p++;
+	}
+	len = strlen(p);
+	if (len && p[len - 1] == '/') {
+		len--;
+		*flags |= EXC_FLAG_MUSTBEDIR;
+	}
+	for (i = 0; i < len; i++) {
+		if (p[i] == '/')
+			break;
+	}
+	if (i == len)
+		*flags |= EXC_FLAG_NODIR;
+	*nowildcardlen = simple_length(p);
+	/*
+	 * we should have excluded the trailing slash from 'p' too,
+	 * but that's one more allocation. Instead just make sure
+	 * nowildcardlen does not exceed real patternlen
+	 */
+	if (*nowildcardlen > len)
+		*nowildcardlen = len;
+	if (*p == '*' && no_wildcard(p + 1))
+		*flags |= EXC_FLAG_ENDSWITH;
+	*pattern = p;
+	*patternlen = len;
+}
+
 void add_exclude(const char *string, const char *base,
 		 int baselen, struct exclude_list *which)
 {
 	struct exclude *x;
-	size_t len;
-	int to_exclude = 1;
-	int flags = 0;
+	int patternlen;
+	int flags;
+	int nowildcardlen;
 
-	if (*string == '!') {
-		to_exclude = 0;
-		string++;
-	}
-	len = strlen(string);
-	if (len && string[len - 1] == '/') {
+	parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
+	if (flags & EXC_FLAG_MUSTBEDIR) {
 		char *s;
-		x = xmalloc(sizeof(*x) + len);
+		x = xmalloc(sizeof(*x) + patternlen + 1);
 		s = (char *)(x+1);
-		memcpy(s, string, len - 1);
-		s[len - 1] = '\0';
-		string = s;
+		memcpy(s, string, patternlen);
+		s[patternlen] = '\0';
 		x->pattern = s;
-		flags = EXC_FLAG_MUSTBEDIR;
 	} else {
 		x = xmalloc(sizeof(*x));
 		x->pattern = string;
 	}
-	x->to_exclude = to_exclude;
-	x->patternlen = strlen(string);
+	x->patternlen = patternlen;
+	x->nowildcardlen = nowildcardlen;
 	x->base = base;
 	x->baselen = baselen;
 	x->flags = flags;
-	if (!strchr(string, '/'))
-		x->flags |= EXC_FLAG_NODIR;
-	x->nowildcardlen = simple_length(string);
-	if (*string == '*' && no_wildcard(string+1))
-		x->flags |= EXC_FLAG_ENDSWITH;
 	ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
 	which->excludes[which->nr++] = x;
 }
@@ -518,7 +545,7 @@ int excluded_from_list(const char *pathname,
 	for (i = el->nr - 1; 0 <= i; i--) {
 		struct exclude *x = el->excludes[i];
 		const char *name, *exclude = x->pattern;
-		int to_exclude = x->to_exclude;
+		int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 		int namelen, prefix = x->nowildcardlen;
 
 		if (x->flags & EXC_FLAG_MUSTBEDIR) {
diff --git a/dir.h b/dir.h
index 893465a..41ea32d 100644
--- a/dir.h
+++ b/dir.h
@@ -11,6 +11,7 @@ struct dir_entry {
 #define EXC_FLAG_NODIR 1
 #define EXC_FLAG_ENDSWITH 4
 #define EXC_FLAG_MUSTBEDIR 8
+#define EXC_FLAG_NEGATIVE 16
 
 struct exclude_list {
 	int nr;
@@ -21,7 +22,6 @@ struct exclude_list {
 		int nowildcardlen;
 		const char *base;
 		int baselen;
-		int to_exclude;
 		int flags;
 	} **excludes;
 };
-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 02/10] attr: avoid strlen() on every match
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 01/10] gitignore: make pattern parsing code a separate function Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 03/10] attr: avoid searching for basename " Nguyễn Thái Ngọc Duy
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 attr.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/attr.c b/attr.c
index b52efb5..2942bf6 100644
--- a/attr.c
+++ b/attr.c
@@ -277,6 +277,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
 static struct attr_stack {
 	struct attr_stack *prev;
 	char *origin;
+	size_t originlen;
 	unsigned num_matches;
 	unsigned alloc;
 	struct match_attr **attrs;
@@ -532,6 +533,7 @@ static void bootstrap_attr_stack(void)
 	if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
 		elem = read_attr(GITATTRIBUTES_FILE, 1);
 		elem->origin = xstrdup("");
+		elem->originlen = 0;
 		elem->prev = attr_stack;
 		attr_stack = elem;
 		debug_push(elem);
@@ -625,7 +627,7 @@ static void prepare_attr_stack(const char *path)
 			strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
 			elem = read_attr(pathbuf.buf, 0);
 			strbuf_setlen(&pathbuf, cp - path);
-			elem->origin = strbuf_detach(&pathbuf, NULL);
+			elem->origin = strbuf_detach(&pathbuf, &elem->originlen);
 			elem->prev = attr_stack;
 			attr_stack = elem;
 			debug_push(elem);
@@ -700,7 +702,7 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
 		if (a->is_macro)
 			continue;
 		if (path_matches(path, pathlen,
-				 a->u.pattern, base, strlen(base)))
+				 a->u.pattern, base, stk->originlen))
 			rem = fill_one("fill", a, rem);
 	}
 	return rem;
-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 03/10] attr: avoid searching for basename on every match
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 01/10] gitignore: make pattern parsing code a separate function Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 02/10] attr: avoid strlen() on every match Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 04/10] attr: more matching optimizations from .gitignore Nguyễn Thái Ngọc Duy
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 attr.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/attr.c b/attr.c
index 2942bf6..aeac564 100644
--- a/attr.c
+++ b/attr.c
@@ -644,13 +644,11 @@ static void prepare_attr_stack(const char *path)
 }
 
 static int path_matches(const char *pathname, int pathlen,
+			const char *basename,
 			const char *pattern,
 			const char *base, int baselen)
 {
 	if (!strchr(pattern, '/')) {
-		/* match basename */
-		const char *basename = strrchr(pathname, '/');
-		basename = basename ? basename + 1 : pathname;
 		return (fnmatch_icase(pattern, basename, 0) == 0);
 	}
 	/*
@@ -692,7 +690,8 @@ static int fill_one(const char *what, struct match_attr *a, int rem)
 	return rem;
 }
 
-static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
+static int fill(const char *path, int pathlen, const char *basename,
+		struct attr_stack *stk, int rem)
 {
 	int i;
 	const char *base = stk->origin ? stk->origin : "";
@@ -701,7 +700,7 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
 		struct match_attr *a = stk->attrs[i];
 		if (a->is_macro)
 			continue;
-		if (path_matches(path, pathlen,
+		if (path_matches(path, pathlen, basename,
 				 a->u.pattern, base, stk->originlen))
 			rem = fill_one("fill", a, rem);
 	}
@@ -740,15 +739,19 @@ static void collect_all_attrs(const char *path)
 {
 	struct attr_stack *stk;
 	int i, pathlen, rem;
+	const char *basename;
 
 	prepare_attr_stack(path);
 	for (i = 0; i < attr_nr; i++)
 		check_all_attr[i].value = ATTR__UNKNOWN;
 
+	basename = strrchr(path, '/');
+	basename = basename ? basename + 1 : path;
+
 	pathlen = strlen(path);
 	rem = attr_nr;
 	for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-		rem = fill(path, pathlen, stk, rem);
+		rem = fill(path, pathlen, basename, stk, rem);
 }
 
 int git_check_attr(const char *path, int num, struct git_attr_check *check)
-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (2 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 03/10] attr: avoid searching for basename " Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05 18:48   ` Junio C Hamano
  2012-10-05  4:41 ` [PATCH 05/10] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

.gitattributes and .gitignore share the same pattern syntax but has
separate matching implementation. Over the years, ignore's
implementation accumulates more optimizations while attr's stays the
same.

This patch adds those optimizations to .gitattributes. Basically it
tries to avoid fnmatch/wildmatch in favor of strncmp as much as
possible.

There are two syntaxes that .gitignore supports but .gitattributes
does not: negative patterns and directory matching. They have never
worked and whether they will is up for future discussion. Meanwhile
make a note in the document and reject such patterns.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitattributes.txt |  2 ++
 attr.c                          | 68 ++++++++++++++++++++++++++++++++++-------
 dir.c                           |  8 ++---
 dir.h                           |  1 +
 4 files changed, 64 insertions(+), 15 deletions(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index e16f3e1..702b8c1 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -56,6 +56,8 @@ When more than one pattern matches the path, a later line
 overrides an earlier line.  This overriding is done per
 attribute.  The rules how the pattern matches paths are the
 same as in `.gitignore` files; see linkgit:gitignore[5].
+Unlike `.gitignore`, negative patterns are not supported.
+Patterns that match directories are also not supported.
 
 When deciding what attributes are assigned to a path, git
 consults `$GIT_DIR/info/attributes` file (which has the highest
diff --git a/attr.c b/attr.c
index aeac564..1aa058e 100644
--- a/attr.c
+++ b/attr.c
@@ -115,6 +115,13 @@ struct attr_state {
 	const char *setto;
 };
 
+struct pattern {
+	const char *pattern;
+	int patternlen;
+	int nowildcardlen;
+	int flags;		/* EXC_FLAG_* */
+};
+
 /*
  * One rule, as from a .gitattributes file.
  *
@@ -131,7 +138,7 @@ struct attr_state {
  */
 struct match_attr {
 	union {
-		char *pattern;
+		struct pattern pat;
 		struct git_attr *attr;
 	} u;
 	char is_macro;
@@ -241,9 +248,18 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
 	if (is_macro)
 		res->u.attr = git_attr_internal(name, namelen);
 	else {
-		res->u.pattern = (char *)&(res->state[num_attr]);
-		memcpy(res->u.pattern, name, namelen);
-		res->u.pattern[namelen] = 0;
+		char *p = (char *)&(res->state[num_attr]);
+		memcpy(p, name, namelen);
+		p[namelen] = 0;
+		res->u.pat.pattern = p;
+		parse_exclude_pattern(&res->u.pat.pattern,
+				      &res->u.pat.patternlen,
+				      &res->u.pat.flags,
+				      &res->u.pat.nowildcardlen);
+		if (res->u.pat.flags & EXC_FLAG_NEGATIVE)
+			die(_("Negative patterns are not supported in git attributes"));
+		if (res->u.pat.flags & EXC_FLAG_MUSTBEDIR)
+			die(_("Directory patterns are not supported in git attributes"));
 	}
 	res->is_macro = is_macro;
 	res->num_attr = num_attr;
@@ -645,25 +661,55 @@ static void prepare_attr_stack(const char *path)
 
 static int path_matches(const char *pathname, int pathlen,
 			const char *basename,
-			const char *pattern,
+			const struct pattern *pat,
 			const char *base, int baselen)
 {
-	if (!strchr(pattern, '/')) {
+	const char *pattern = pat->pattern;
+	int prefix = pat->nowildcardlen;
+	const char *name;
+	int namelen;
+
+	if (pat->flags & EXC_FLAG_NODIR) {
+		if (prefix == pat->patternlen &&
+		    !strcmp_icase(pattern, basename))
+			return 1;
+
+		if (pat->flags & EXC_FLAG_ENDSWITH &&
+		    pat->patternlen - 1 <= pathlen &&
+		    !strcmp_icase(pattern + 1, pathname +
+				  pathlen - pat->patternlen + 1))
+			return 1;
+
 		return (fnmatch_icase(pattern, basename, 0) == 0);
 	}
 	/*
 	 * match with FNM_PATHNAME; the pattern has base implicitly
 	 * in front of it.
 	 */
-	if (*pattern == '/')
+	if (*pattern == '/') {
 		pattern++;
+		prefix--;
+	}
+
+	/*
+	 * note: unlike excluded_from_list, baselen here does not
+	 * contain the trailing slash
+	 */
+
 	if (pathlen < baselen ||
 	    (baselen && pathname[baselen] != '/') ||
 	    strncmp(pathname, base, baselen))
 		return 0;
-	if (baselen != 0)
-		baselen++;
-	return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+
+	namelen = baselen ? pathlen - baselen - 1 : pathlen;
+	name = pathname + pathlen - namelen;
+
+	/* if the non-wildcard part is longer than the remaining
+	   pathname, surely it cannot match */
+	if (!namelen || prefix > namelen)
+		return 0;
+
+	return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
 }
 
 static int macroexpand_one(int attr_nr, int rem);
@@ -701,7 +747,7 @@ static int fill(const char *path, int pathlen, const char *basename,
 		if (a->is_macro)
 			continue;
 		if (path_matches(path, pathlen, basename,
-				 a->u.pattern, base, stk->originlen))
+				 &a->u.pat, base, stk->originlen))
 			rem = fill_one("fill", a, rem);
 	}
 	return rem;
diff --git a/dir.c b/dir.c
index cd13920..c6a0275 100644
--- a/dir.c
+++ b/dir.c
@@ -308,10 +308,10 @@ static int no_wildcard(const char *string)
 	return string[simple_length(string)] == '\0';
 }
 
-static void parse_exclude_pattern(const char **pattern,
-				  int *patternlen,
-				  int *flags,
-				  int *nowildcardlen)
+void parse_exclude_pattern(const char **pattern,
+			   int *patternlen,
+			   int *flags,
+			   int *nowildcardlen)
 {
 	const char *p = *pattern;
 	size_t i, len;
diff --git a/dir.h b/dir.h
index 41ea32d..fd5c2aa 100644
--- a/dir.h
+++ b/dir.h
@@ -97,6 +97,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
 					  char **buf_p, struct exclude_list *which, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
 			int baselen, struct exclude_list *which);
 extern void free_excludes(struct exclude_list *el);
-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 05/10] Import wildmatch from rsync
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (3 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 04/10] attr: more matching optimizations from .gitignore Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05 10:30   ` Peter Krefting
  2012-10-05  4:41 ` [PATCH 06/10] wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

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

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

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

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

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

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

* [PATCH 06/10] wildmatch: remove static variable force_lower_case
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (4 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 05/10] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 07/10] wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

One place less to worry about thread safety

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

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

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

* [PATCH 07/10] wildmatch: fix case-insensitive matching
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (5 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 06/10] wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 08/10] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

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

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

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

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

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

* [PATCH 08/10] Integrate wildmatch to git
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (6 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 07/10] wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05 21:20   ` Thiago Farina
  2012-10-05  4:41 ` [PATCH 09/10] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 10/10] gitignore: forbid "abc**def" Nguyễn Thái Ngọc Duy
  9 siblings, 1 reply; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty,
	Nguyễn Thái Ngọc Duy, Ramsay Jones

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

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

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

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

* [PATCH 09/10] Support "**" in .gitignore and .gitattributes patterns using wildmatch()
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (7 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 08/10] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  2012-10-05  4:41 ` [PATCH 10/10] gitignore: forbid "abc**def" Nguyễn Thái Ngọc Duy
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

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

diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index c1f692a..eb81d31 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -93,6 +93,9 @@ PATTERN FORMAT
    For example, "Documentation/{asterisk}.html" matches
    "Documentation/git.html" but not "Documentation/ppc/ppc.html"
    or "tools/perf/Documentation/perf.html".
++
+Contrary to fnmatch(3), git matches "**" to anything including
+slashes, similar to rsync(1).
 
  - A leading slash matches the beginning of the pathname.
    For example, "/{asterisk}.c" matches "cat-file.c" but not
diff --git a/attr.c b/attr.c
index 1aa058e..3103c66 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";
@@ -709,7 +710,8 @@ static int path_matches(const char *pathname, int pathlen,
 	if (!namelen || prefix > namelen)
 		return 0;
 
-	return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
+	return (ignore_case && iwildmatch(pattern, name)) ||
+		(!ignore_case && wildmatch(pattern, name));
 }
 
 static int macroexpand_one(int attr_nr, int rem);
diff --git a/dir.c b/dir.c
index c6a0275..cb78273 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;
@@ -600,7 +601,9 @@ int excluded_from_list(const char *pathname,
 			namelen -= prefix;
 		}
 
-		if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))
+		if (!namelen ||
+		    ((ignore_case && iwildmatch(exclude, name)) ||
+		     (!ignore_case && wildmatch(exclude, name))))
 			return to_exclude;
 	}
 	return -1; /* undecided */
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index febc45c..6c3c554 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -232,4 +232,21 @@ test_expect_success 'bare repository: test info/attributes' '
 	attr_check subdir/a/i unspecified
 '
 
+test_expect_success '"**" test' '
+	cd .. &&
+	echo "**/f foo=bar" >.gitattributes &&
+	cat <<\EOF >expect &&
+f: foo: unspecified
+a/f: foo: bar
+a/b/f: foo: bar
+a/b/c/f: foo: bar
+EOF
+	git check-attr foo -- "f" >actual 2>err &&
+	git check-attr foo -- "a/f" >>actual 2>>err &&
+	git check-attr foo -- "a/b/f" >>actual 2>>err &&
+	git check-attr foo -- "a/b/c/f" >>actual 2>>err &&
+	test_cmp expect actual &&
+	test_line_count = 0 err
+'
+
 test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index c8fe978..67c8bcf 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -214,4 +214,15 @@ test_expect_success 'subdirectory ignore (l1)' '
 	test_cmp expect actual
 '
 
+
+test_expect_success 'ls-files with "**" patterns' '
+	cat <<\EOF >expect &&
+one/a.1
+one/two/a.1
+three/a.1
+EOF
+	git ls-files -o -i --exclude "**/a.1" >actual
+	test_cmp expect actual
+'
+
 test_done
-- 
1.7.12.1.405.gb727dc9

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

* [PATCH 10/10] gitignore: forbid "abc**def"
  2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
                   ` (8 preceding siblings ...)
  2012-10-05  4:41 ` [PATCH 09/10] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
@ 2012-10-05  4:41 ` Nguyễn Thái Ngọc Duy
  9 siblings, 0 replies; 22+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2012-10-05  4:41 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Michael Haggerty, Nguyễn Thái Ngọc Duy

Deep down wildmatch() sees "**" as "*" that can also match slashes. On
the surface, it may be confusing to users as the above pattern can
match "abcdef", "abcxyzdef", "abc/def", "abc/x/def",
"abc/x/y/def"... For now we just forbid it. Users can only do
"**/def", "abc/**" or "abc/**/def". The syntax may be re-enabled in
future.

There's a minor problem with this particular approach. "**" inside
square brackets are mistaken as the wildcard while they are not. Git
shows a confusing message when users do that.

Note that this patch hides a potential problem that if "abc**def" is
ever supported, EXC_FLAG_NODIR flag should be turned off or only the
base name is matched against the pattern, which makes no sense.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitignore.txt        |  6 ++++--
 dir.c                              | 12 +++++++++---
 t/t0003-attributes.sh              |  5 +++++
 t/t3001-ls-files-others-exclude.sh |  5 +++++
 4 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index eb81d31..ad9fc2f 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -94,8 +94,10 @@ PATTERN FORMAT
    "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).
+In addition to fnmatch(3) syntax, "**" can be used to match one or
+more directories. For example, "abc/**/def" matches "abc/x/def",
+"abc/x/y/def", "abc/x/y/z/def" and so on. "**" must be wrapped by
+slashes.
 
  - A leading slash matches the beginning of the pathname.
    For example, "/{asterisk}.c" matches "cat-file.c" but not
diff --git a/dir.c b/dir.c
index cb78273..f30117f 100644
--- a/dir.c
+++ b/dir.c
@@ -327,12 +327,18 @@ void parse_exclude_pattern(const char **pattern,
 		len--;
 		*flags |= EXC_FLAG_MUSTBEDIR;
 	}
+	*flags |= EXC_FLAG_NODIR;
 	for (i = 0; i < len; i++) {
 		if (p[i] == '/')
-			break;
+			*flags &= ~EXC_FLAG_NODIR;
+		if ((p[i] == '*' && p[i + 1] == '*' &&
+		     (i == 0 || p[i - 1] != '\\')) &&
+		    !((i == 0 || p[i - 1] == '/') &&
+		      (p[i + 2] == '\0' ||
+		       p[i + 2] == '/' ||
+		       (p[i + 2] == '\\' && p[i + 3] == '/'))))
+			die(_("** in .gitignore or .gitattributes must be wrapped by slashes"));
 	}
-	if (i == len)
-		*flags |= EXC_FLAG_NODIR;
 	*nowildcardlen = simple_length(p);
 	/*
 	 * we should have excluded the trailing slash from 'p' too,
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 6c3c554..ddeb321 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -249,4 +249,9 @@ EOF
 	test_line_count = 0 err
 '
 
+test_expect_success '"**" with no slashes test' '
+	echo "a**f foo=bar" >.gitattributes &&
+	test_must_fail git check-attr foo -- "f"
+'
+
 test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 67c8bcf..f5c62d0 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -225,4 +225,9 @@ EOF
 	test_cmp expect actual
 '
 
+
+test_expect_success 'ls-files with "**" patterns and no slashes' '
+	test_must_fail git ls-files -o -i --exclude "one**a.1"
+'
+
 test_done
-- 
1.7.12.1.405.gb727dc9

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

* Re: [PATCH 05/10] Import wildmatch from rsync
  2012-10-05  4:41 ` [PATCH 05/10] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
@ 2012-10-05 10:30   ` Peter Krefting
  2012-10-05 11:18     ` Nguyen Thai Ngoc Duy
  0 siblings, 1 reply; 22+ messages in thread
From: Peter Krefting @ 2012-10-05 10:30 UTC (permalink / raw)
  To: pclouds; +Cc: Git Mailing List, Junio C Hamano, Michael Haggerty

> These files are from rsync.git commit
> f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit
> before rsync turned GPL-3.

However:

> diff --git a/test-wildmatch.c b/test-wildmatch.c
[...]
> + * 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.

-- 
\\// Peter - http://www.softwolves.pp.se/

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

* Re: [PATCH 05/10] Import wildmatch from rsync
  2012-10-05 10:30   ` Peter Krefting
@ 2012-10-05 11:18     ` Nguyen Thai Ngoc Duy
  0 siblings, 0 replies; 22+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2012-10-05 11:18 UTC (permalink / raw)
  To: Peter Krefting; +Cc: Git Mailing List, Junio C Hamano, Michael Haggerty

On Fri, Oct 5, 2012 at 5:30 PM, Peter Krefting <peter@softwolves.pp.se> wrote:
>> These files are from rsync.git commit
>> f92f5b166e3019db42bc7fe1aa2f1a9178cd215d, which was the last commit
>> before rsync turned GPL-3.
>
>
> However:
>
>> diff --git a/test-wildmatch.c b/test-wildmatch.c
>
> [...]
>
>> + * 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.

A copy mistake. I probably did not look closely at this file as it's
not the main source, we could even rewrite it with little effort. Will
update with GPL2 version next round.
-- 
Duy

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-05  4:41 ` [PATCH 04/10] attr: more matching optimizations from .gitignore Nguyễn Thái Ngọc Duy
@ 2012-10-05 18:48   ` Junio C Hamano
  2012-10-06  5:02     ` Nguyen Thai Ngoc Duy
  2012-10-08  3:26     ` Nguyen Thai Ngoc Duy
  0 siblings, 2 replies; 22+ messages in thread
From: Junio C Hamano @ 2012-10-05 18:48 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git, Michael Haggerty

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

> +Unlike `.gitignore`, negative patterns are not supported.
> +Patterns that match directories are also not supported.

Is "are not supported" the right phrasing?

I think it makes perfect sense not to forbid "!path attr1", because
it is unclear what it means (e.g. "path -attr1" vs "path !attr1").
So I would say "Negative patterns are forbidden as they do not make
any sense".

But for the latter, I think it makes a lot more sense to just accept
"path/ attr1" and doing nothing.  The user requests to set an
attribute to "path" that has to be a directory, and there is nothing
wrong in such a request in itself.  But nothing in git asks for
attributes for directories (because we do not track directories),
and such a request happens to be a no-op.

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

* Re: [PATCH 08/10] Integrate wildmatch to git
  2012-10-05  4:41 ` [PATCH 08/10] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
@ 2012-10-05 21:20   ` Thiago Farina
  2012-10-06  9:25     ` Joachim Schmitz
  0 siblings, 1 reply; 22+ messages in thread
From: Thiago Farina @ 2012-10-05 21:20 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, Junio C Hamano, Michael Haggerty, Ramsay Jones

On Fri, Oct 5, 2012 at 1:41 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> This makes wildmatch.c part of libgit.a and builds test-wildmatch; the
> dependency on libpopt in the original has been replaced with the use
> of our parse-options. Global variables in test-wildmatch are marked
> static to avoid sparse warnings.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  .gitignore           |  1 +
>  Makefile             |  3 ++
>  t/t3070-wildmatch.sh | 27 ++++++++++++++++
>  test-wildmatch.c     | 88 ++++++++++++++++++++++------------------------------
>  wildmatch.c          | 26 +++++-----------
>  5 files changed, 75 insertions(+), 70 deletions(-)
>  create mode 100755 t/t3070-wildmatch.sh
>
> diff --git a/test-wildmatch.c b/test-wildmatch.c
> index 88585c2..bb726c8 100644
> --- a/test-wildmatch.c
> +++ b/test-wildmatch.c
> @@ -19,34 +19,38 @@
>
>  /*#define COMPARE_WITH_FNMATCH*/
>
> -#define WILD_TEST_ITERATIONS
> -#include "lib/wildmatch.c"
> +#include "cache.h"
> +#include "parse-options.h"
> +#include "wildmatch.h"
>
> -#include <popt.h>
> +#ifndef MAXPATHLEN
> +#define MAXPATHLEN 1024
> +#endif
> +#ifdef NO_STRLCPY
> +#include "compat/strlcpy.c"
> +#define strlcpy gitstrlcpy
> +#endif
>
>  #ifdef COMPARE_WITH_FNMATCH
>  #include <fnmatch.h>
>
> -int fnmatch_errors = 0;
> +static int fnmatch_errors = 0;
>  #endif
>
> -int wildmatch_errors = 0;
> -char number_separator = ',';
> +static int wildmatch_errors = 0;
>
>  typedef char bool;
>
> -int output_iterations = 0;
> -int explode_mod = 0;
> -int empties_mod = 0;
> -int empty_at_start = 0;
> -int empty_at_end = 0;
> -
> -static struct poptOption long_options[] = {
> -  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
> -  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
> -  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
> -  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
> -  {0,0,0,0, 0, 0, 0}
> +static int explode_mod = 0;
Isn't static variables like this initialized to zero by default? There
is a high chance that I might be wrong though.

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-05 18:48   ` Junio C Hamano
@ 2012-10-06  5:02     ` Nguyen Thai Ngoc Duy
  2012-10-06  5:36       ` Junio C Hamano
  2012-10-08  3:26     ` Nguyen Thai Ngoc Duy
  1 sibling, 1 reply; 22+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2012-10-06  5:02 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty

On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>
>> +Unlike `.gitignore`, negative patterns are not supported.
>> +Patterns that match directories are also not supported.
>
> Is "are not supported" the right phrasing?
>
> I think it makes perfect sense not to forbid "!path attr1", because
> it is unclear what it means (e.g. "path -attr1" vs "path !attr1").
> So I would say "Negative patterns are forbidden as they do not make
> any sense".

OK

> But for the latter, I think it makes a lot more sense to just accept
> "path/ attr1" and doing nothing.  The user requests to set an
> attribute to "path" that has to be a directory, and there is nothing
> wrong in such a request in itself.  But nothing in git asks for
> attributes for directories (because we do not track directories),
> and such a request happens to be a no-op.

Or the user might think "path/ attr1" sets attr1 for all files under
"path/" because it does not make sense to attach attributes to a
directory in git.
-- 
Duy

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-06  5:02     ` Nguyen Thai Ngoc Duy
@ 2012-10-06  5:36       ` Junio C Hamano
  2012-10-06  6:43         ` Nguyen Thai Ngoc Duy
  0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2012-10-06  5:36 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git, Michael Haggerty

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:

> On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>>
>>> +Unlike `.gitignore`, negative patterns are not supported.
>>> +Patterns that match directories are also not supported.
>>
>> Is "are not supported" the right phrasing?
>>
>> I think it makes perfect sense not to forbid "!path attr1", because
>> it is unclear what it means (e.g. "path -attr1" vs "path !attr1").
>> So I would say "Negative patterns are forbidden as they do not make
>> any sense".
>
> OK
>
>> But for the latter, I think it makes a lot more sense to just accept
>> "path/ attr1" and doing nothing.  The user requests to set an
>> attribute to "path" that has to be a directory, and there is nothing
>> wrong in such a request in itself.  But nothing in git asks for
>> attributes for directories (because we do not track directories),
>> and such a request happens to be a no-op.
>
> Or the user might think "path/ attr1" sets attr1 for all files under
> "path/" because it does not make sense to attach attributes to a
> directory in git.

Hrm, isn't that reasoning flawed at least for two reasons?

 - One reasonable way to conceptually unify excludes and attributes
   is to consider "being ignored" as just one kind of attribute. If
   you imagine an ideal world where we did not have any .gitignore,
   an entry "path/" in .gitignore would be "path/ excluded", and an
   entry "!path/" in .gitignore would be "path/ -excluded".  Realize
   that on the exclude side, we are already assigning an "attribute"
   to a directory.

 - Setting attr1 to everything in path is spelled "path1/**/* attr1"
   if we are to adopt "/**/ is zero or more intermediate levels"
   enhancement.

   We may not have a need to assign a real attribute to a directory
   right now, because nothing in Git asks for an attribute for a
   directory. But that does not necessarily mean we would never need a
   way to give an attribute to a directory but not to its contents.

If one does not think it through, the "path/ excluded" example might
appear that there is no difference between setting exclude to the
path itself and setting it to path and everything underneath it, but
that comes largely from the nature of "exclude" attribute (think of
"exclude" attribute as "exclude itself and everything under it).

There is no reason to assume other attributes we may want to give to
a directory share the same "recursive" kind of semantics.

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-06  5:36       ` Junio C Hamano
@ 2012-10-06  6:43         ` Nguyen Thai Ngoc Duy
  2012-10-06  6:59           ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2012-10-06  6:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty

On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Or the user might think "path/ attr1" sets attr1 for all files under
>> "path/" because it does not make sense to attach attributes to a
>> directory in git.
>
>    ...
>
>    We may not have a need to assign a real attribute to a directory
>    right now, because nothing in Git asks for an attribute for a
>    directory. But that does not necessarily mean we would never need a
>    way to give an attribute to a directory but not to its contents.

Exactly why we should not make "path/ attr" no-op. If we want to make
it meaningful some day in future, I don't think we want those no-ops
lay around and suddenly cause changes in behavior with a new version
of git.

> If one does not think it through, the "path/ excluded" example might
> appear that there is no difference between setting exclude to the
> path itself and setting it to path and everything underneath it, but
> that comes largely from the nature of "exclude" attribute (think of
> "exclude" attribute as "exclude itself and everything under it).

>From a user perspective, the thinking through portion is usually less
than the try-and-see.

> There is no reason to assume other attributes we may want to give to
> a directory share the same "recursive" kind of semantics.
-- 
Duy

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-06  6:43         ` Nguyen Thai Ngoc Duy
@ 2012-10-06  6:59           ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2012-10-06  6:59 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git, Michael Haggerty

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:

> On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>> Or the user might think "path/ attr1" sets attr1 for all files under
>>> "path/" because it does not make sense to attach attributes to a
>>> directory in git.
>>
>>    ...
>>
>>    We may not have a need to assign a real attribute to a directory
>>    right now, because nothing in Git asks for an attribute for a
>>    directory. But that does not necessarily mean we would never need a
>>    way to give an attribute to a directory but not to its contents.
>
> Exactly why we should not make "path/ attr" no-op. If we want to make
> it meaningful some day in future, I don't think we want those no-ops
> lay around and suddenly cause changes in behavior with a new version
> of git.

I do not think you understood.  "path/ attr" is a no-op from the
point of view of the *users* of the current versions of Git.  It is
perfectly fine to accept and apply attr to "path/"; no codepath in
Git should be asking about path/ anyway, so it ends up to be a
no-op.  You shouldn't be erroring out at the syntactic level, i.e.
when these lines are parsed.

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

* Re: [PATCH 08/10] Integrate wildmatch to git
  2012-10-05 21:20   ` Thiago Farina
@ 2012-10-06  9:25     ` Joachim Schmitz
  0 siblings, 0 replies; 22+ messages in thread
From: Joachim Schmitz @ 2012-10-06  9:25 UTC (permalink / raw)
  To: git

Thiago Farina wrote:
> On Fri, Oct 5, 2012 at 1:41 AM, Nguyễn Thái Ngọc Duy
> <pclouds@gmail.com> wrote:
>> This makes wildmatch.c part of libgit.a and builds test-wildmatch;
>> the dependency on libpopt in the original has been replaced with the
>> use
>> of our parse-options. Global variables in test-wildmatch are marked
>> static to avoid sparse warnings.
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>  .gitignore           |  1 +
>>  Makefile             |  3 ++
>>  t/t3070-wildmatch.sh | 27 ++++++++++++++++
>>  test-wildmatch.c     | 88
>>  ++++++++++++++++++++++------------------------------ wildmatch.c
>>  | 26 +++++----------- 5 files changed, 75 insertions(+), 70
>>  deletions(-) create mode 100755 t/t3070-wildmatch.sh
>>
>> diff --git a/test-wildmatch.c b/test-wildmatch.c
>> index 88585c2..bb726c8 100644
>> --- a/test-wildmatch.c
>> +++ b/test-wildmatch.c
>> @@ -19,34 +19,38 @@
>>
>>  /*#define COMPARE_WITH_FNMATCH*/
>>
>> -#define WILD_TEST_ITERATIONS
>> -#include "lib/wildmatch.c"
>> +#include "cache.h"
>> +#include "parse-options.h"
>> +#include "wildmatch.h"
>>
>> -#include <popt.h>
>> +#ifndef MAXPATHLEN
>> +#define MAXPATHLEN 1024
>> +#endif
>> +#ifdef NO_STRLCPY
>> +#include "compat/strlcpy.c"
>> +#define strlcpy gitstrlcpy
>> +#endif
>>
>>  #ifdef COMPARE_WITH_FNMATCH
>>  #include <fnmatch.h>
>>
>> -int fnmatch_errors = 0;
>> +static int fnmatch_errors = 0;
>>  #endif
>>
>> -int wildmatch_errors = 0;
>> -char number_separator = ',';
>> +static int wildmatch_errors = 0;
>>
>>  typedef char bool;
>>
>> -int output_iterations = 0;
>> -int explode_mod = 0;
>> -int empties_mod = 0;
>> -int empty_at_start = 0;
>> -int empty_at_end = 0;
>> -
>> -static struct poptOption long_options[] = {
>> -  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc
>> */
>> -  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0,
>> 0, 0},
>> -  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
>> -  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
>> -  {0,0,0,0, 0, 0, 0}
>> +static int explode_mod = 0;
> Isn't static variables like this initialized to zero by default? There
> is a high chance that I might be wrong though.

C99,
5.1.2.1: All objects with static storage duration shall be initialized (set 
to their initial values) before program startup.
6.2.4.2: An object whose identifier is declared with external or internal 
linkage, or with the storage-class specifier static has static storage 
duration. Its lifetime is the entire execution of the program and its stored 
value is initialized only once, prior to program startup.
6.7.8.10: If an object that has automatic storage duration is not 
initialized explicitly, its value is indeterminate. If an object that has 
static storage duration is not initialized explicitly, then:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) 
zero;
— if it is an aggregate, every member is initialized (recursively) according 
to these rules;
— if it is a union, the first named member is initialized (recursively) 
according to these rules.

So seems you're right ;-) 

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-05 18:48   ` Junio C Hamano
  2012-10-06  5:02     ` Nguyen Thai Ngoc Duy
@ 2012-10-08  3:26     ` Nguyen Thai Ngoc Duy
  2012-10-08 15:50       ` Junio C Hamano
  1 sibling, 1 reply; 22+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2012-10-08  3:26 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty

On Sat, Oct 6, 2012 at 1:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:
>
>> +Unlike `.gitignore`, negative patterns are not supported.
>> +Patterns that match directories are also not supported.
>
> Is "are not supported" the right phrasing?
>
> I think it makes perfect sense not to forbid "!path attr1", because
> it is unclear what it means (e.g. "path -attr1" vs "path !attr1").
> So I would say "Negative patterns are forbidden as they do not make
> any sense".

!path/sub/ alone does not mean anything. It must be used together with
a positive pattern to define the set of paths the same attribute
assignment statement applies to. This makes sense (attr, -attr1 or
!attr1 are all OK):

*.c attr1
!foo.c attr1

But this does not (actually "!foo.c" line has no effects because of
different attribute assignments):

*.c attr1
!foo.c attr2

It could be even more confusing in multiple attribute manipulation:

*.c attr1
*.h -attr2
!foo.[ch] attr1 -attr2

So "not supported" and "forbidden" are equally OK. I just want to
raise a point that it has some use before we go for "forbidden".

> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>
>> On Sat, Oct 6, 2012 at 12:36 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>> Or the user might think "path/ attr1" sets attr1 for all files under
>>>> "path/" because it does not make sense to attach attributes to a
>>>> directory in git.
>>>
>>>    ...
>>>
>>>    We may not have a need to assign a real attribute to a directory
>>>    right now, because nothing in Git asks for an attribute for a
>>>    directory. But that does not necessarily mean we would never need a
>>>    way to give an attribute to a directory but not to its contents.
>>
>> Exactly why we should not make "path/ attr" no-op. If we want to make
>> it meaningful some day in future, I don't think we want those no-ops
>> lay around and suddenly cause changes in behavior with a new version
>> of git.
>
> I do not think you understood.  "path/ attr" is a no-op from the
> point of view of the *users* of the current versions of Git.  It is
> perfectly fine to accept and apply attr to "path/"; no codepath in
> Git should be asking about path/ anyway, so it ends up to be a
> no-op.  You shouldn't be erroring out at the syntactic level, i.e.
> when these lines are parsed.

My objection is no-op lines are timed bombs. But users can already do
"dir attr" (no slashes), which is no-op. So yeah, no-op is fine.
-- 
Duy

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

* Re: [PATCH 04/10] attr: more matching optimizations from .gitignore
  2012-10-08  3:26     ` Nguyen Thai Ngoc Duy
@ 2012-10-08 15:50       ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2012-10-08 15:50 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git, Michael Haggerty

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:

> My objection is no-op lines are timed bombs. But users can already do
> "dir attr" (no slashes), which is no-op. So yeah, no-op is fine.

Exactly. If you are not catching and barfing the no-slashed variant
at the syntax level (and you shouldn't), you shouldn't do so for
slashed ones.

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

end of thread, other threads:[~2012-10-08 15:50 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-10-05  4:40 [PATCH 00/10] nd/wildmatch take 2 Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 01/10] gitignore: make pattern parsing code a separate function Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 02/10] attr: avoid strlen() on every match Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 03/10] attr: avoid searching for basename " Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 04/10] attr: more matching optimizations from .gitignore Nguyễn Thái Ngọc Duy
2012-10-05 18:48   ` Junio C Hamano
2012-10-06  5:02     ` Nguyen Thai Ngoc Duy
2012-10-06  5:36       ` Junio C Hamano
2012-10-06  6:43         ` Nguyen Thai Ngoc Duy
2012-10-06  6:59           ` Junio C Hamano
2012-10-08  3:26     ` Nguyen Thai Ngoc Duy
2012-10-08 15:50       ` Junio C Hamano
2012-10-05  4:41 ` [PATCH 05/10] Import wildmatch from rsync Nguyễn Thái Ngọc Duy
2012-10-05 10:30   ` Peter Krefting
2012-10-05 11:18     ` Nguyen Thai Ngoc Duy
2012-10-05  4:41 ` [PATCH 06/10] wildmatch: remove static variable force_lower_case Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 07/10] wildmatch: fix case-insensitive matching Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 08/10] Integrate wildmatch to git Nguyễn Thái Ngọc Duy
2012-10-05 21:20   ` Thiago Farina
2012-10-06  9:25     ` Joachim Schmitz
2012-10-05  4:41 ` [PATCH 09/10] Support "**" in .gitignore and .gitattributes patterns using wildmatch() Nguyễn Thái Ngọc Duy
2012-10-05  4:41 ` [PATCH 10/10] gitignore: forbid "abc**def" Nguyễn Thái Ngọc Duy

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

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

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