Subject: Re: [PROPOSAL] .gitignore syntax modification

From: Nguyen Thai Ngoc Duy <[email-masked]>
Date: Wed, 13 Oct 2010 19:15:52 +0700

On Tue, Oct 12, 2010 at 07:32:54PM -0700, Kevin Ballard wrote:
> As soon as I find the time, I'll be working on a patch for this. I
> only wrote up the proposal because I want to make sure that what I
> end up implementing is actually something that will be accepted. At
> this point I'm actually in favor of simply assuming all paths that
> don't start with / can be matched at any level but I recognize that
> this is a change to existing functionality, though I personally
> think that all patterns that are meant to match only at the current
> level should be prefixed with / anyway. Would such a change to
> existing behavior be rejected out-of-hand?

Yes, patterns that only match current level should be prefixed with a
slash. There are also other cases apart from "current level only" and
"any level": foo/a* will match only second level, not any level.

I was thinking of doing like this. It's not complete (not even build)
but it shows the idea. I don't think this way it will change existing
behaviors. Performance is something I haven't thought through.

Anyway, what do you think? I'm afraid I don't have time to work on
this. The pathspec unification work still needs to be done.
-- 
Duy
>>From 058ed97a52a3a155655193d0ecd94506781c93c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= <[email-masked]>
Date: Wed, 13 Oct 2010 19:01:11 +0700
Subject: [PATCH 1/3] excluded_from_list: split the core exclude code out as a separate function

---
 dir.c |  114 ++++++++++++++++++++++++++++++++++------------------------------
 1 files changed, 61 insertions(+), 53 deletions(-)

diff --git a/dir.c b/dir.c
index a44c7b3..56974e5 100644
--- a/dir.c
+++ b/dir.c
@@ -343,6 +343,64 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 	dir->basebuf[baselen] = '\0';
 }
 
+static int excluded_from_list_1(const char *pathname, int pathlen, const char *basename, struct exclude *x, int *dtype)
+{
+	const char *exclude = x->pattern;
+	int to_exclude = x->to_exclude;
+
+	if (x->flags & EXC_FLAG_MUSTBEDIR) {
+		if (!dtype) {
+			if (!prefixcmp(pathname, exclude))
+				return to_exclude;
+			else
+				return -1;
+		}
+		if (*dtype == DT_UNKNOWN)
+			*dtype = get_dtype(NULL, pathname, pathlen);
+		if (*dtype != DT_DIR)
+			return -1;
+	}
+
+	if (x->flags & EXC_FLAG_NODIR) {
+		/* match basename */
+		if (x->flags & EXC_FLAG_NOWILDCARD) {
+			if (!strcmp(exclude, basename))
+				return to_exclude;
+		} else if (x->flags & EXC_FLAG_ENDSWITH) {
+			if (x->patternlen - 1 <= pathlen &&
+			    !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+				return to_exclude;
+		} else {
+			if (fnmatch(exclude, basename, 0) == 0)
+				return to_exclude;
+		}
+	}
+	else {
+		/* match with FNM_PATHNAME:
+		 * exclude has base (baselen long) implicitly
+		 * in front of it.
+		 */
+		int baselen = x->baselen;
+		if (*exclude == '/')
+			exclude++;
+
+		if (pathlen < baselen ||
+		    (baselen && pathname[baselen-1] != '/') ||
+		    strncmp(pathname, x->base, baselen))
+		    return -1;
+
+		if (x->flags & EXC_FLAG_NOWILDCARD) {
+			if (!strcmp(exclude, pathname + baselen))
+				return to_exclude;
+		} else {
+			if (fnmatch(exclude, pathname+baselen,
+				    FNM_PATHNAME) == 0)
+			    return to_exclude;
+		}
+	}
+	return -1;
+}
+
 /* Scan the list and let the last match determine the fate.
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
@@ -355,59 +413,9 @@ int excluded_from_list(const char *pathname,
 	if (el->nr) {
 		for (i = el->nr - 1; 0 <= i; i--) {
 			struct exclude *x = el->excludes[i];
-			const char *exclude = x->pattern;
-			int to_exclude = x->to_exclude;
-
-			if (x->flags & EXC_FLAG_MUSTBEDIR) {
-				if (!dtype) {
-					if (!prefixcmp(pathname, exclude))
-						return to_exclude;
-					else
-						continue;
-				}
-				if (*dtype == DT_UNKNOWN)
-					*dtype = get_dtype(NULL, pathname, pathlen);
-				if (*dtype != DT_DIR)
-					continue;
-			}
-
-			if (x->flags & EXC_FLAG_NODIR) {
-				/* match basename */
-				if (x->flags & EXC_FLAG_NOWILDCARD) {
-					if (!strcmp(exclude, basename))
-						return to_exclude;
-				} else if (x->flags & EXC_FLAG_ENDSWITH) {
-					if (x->patternlen - 1 <= pathlen &&
-					    !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
-						return to_exclude;
-				} else {
-					if (fnmatch(exclude, basename, 0) == 0)
-						return to_exclude;
-				}
-			}
-			else {
-				/* match with FNM_PATHNAME:
-				 * exclude has base (baselen long) implicitly
-				 * in front of it.
-				 */
-				int baselen = x->baselen;
-				if (*exclude == '/')
-					exclude++;
-
-				if (pathlen < baselen ||
-				    (baselen && pathname[baselen-1] != '/') ||
-				    strncmp(pathname, x->base, baselen))
-				    continue;
-
-				if (x->flags & EXC_FLAG_NOWILDCARD) {
-					if (!strcmp(exclude, pathname + baselen))
-						return to_exclude;
-				} else {
-					if (fnmatch(exclude, pathname+baselen,
-						    FNM_PATHNAME) == 0)
-					    return to_exclude;
-				}
-			}
+			int to_exclude = excluded_from_list_1(pathname, pathlen, basename, x, dtype);
+			if (to_exclude != -1)
+				return to_exclude;
 		}
 	}
 	return -1; /* undecided */
-- 
1.7.0.2.445.gcbdb3

>>From afa6ef0ee1a299aef486367792f6dcc6e4e686d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= <[email-masked]>
Date: Wed, 13 Oct 2010 19:10:35 +0700
Subject: [PATCH 2/3] Set EXC_FLAG_STARSTAR if a pattern starts with "**/"

---
 dir.c |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/dir.c b/dir.c
index 56974e5..241fbce 100644
--- a/dir.c
+++ b/dir.c
@@ -171,6 +171,10 @@ void add_exclude(const char *string, const char *base,
 		to_exclude = 0;
 		string++;
 	}
+	if (*string == '*' && string[1] == '*' && string[2] == '/') {
+		flags |= EXC_FLAG_STARSTAR;
+		string += 3;
+	}
 	len = strlen(string);
 	if (len && string[len - 1] == '/') {
 		char *s;
@@ -180,7 +184,7 @@ void add_exclude(const char *string, const char *base,
 		s[len - 1] = '\0';
 		string = s;
 		x->pattern = s;
-		flags = EXC_FLAG_MUSTBEDIR;
+		flags |= EXC_FLAG_MUSTBEDIR;
 	} else {
 		x = xmalloc(sizeof(*x));
 		x->pattern = string;
@@ -190,7 +194,7 @@ void add_exclude(const char *string, const char *base,
 	x->base = base;
 	x->baselen = baselen;
 	x->flags = flags;
-	if (!strchr(string, '/'))
+	if (!(x->flags & EXC_FLAG_STARSTAR) && !strchr(string, '/'))
 		x->flags |= EXC_FLAG_NODIR;
 	if (no_wildcard(string))
 		x->flags |= EXC_FLAG_NOWILDCARD;
-- 
1.7.0.2.445.gcbdb3

>>From c11a949328b90aca3398f730a50eab35033f0334 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= <[email-masked]>
Date: Wed, 13 Oct 2010 19:06:07 +0700
Subject: [PATCH 3/3] excluded_from_list: support EXC_FLAG_STARSTAR

if that flag is set, try to match all full pathname, then skip one
directory and try again until it completely fails.
---
 dir.c |   20 +++++++++++++++++---
 1 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/dir.c b/dir.c
index 241fbce..11c3e7d 100644
--- a/dir.c
+++ b/dir.c
@@ -417,9 +417,23 @@ int excluded_from_list(const char *pathname,
 	if (el->nr) {
 		for (i = el->nr - 1; 0 <= i; i--) {
 			struct exclude *x = el->excludes[i];
-			int to_exclude = excluded_from_list_1(pathname, pathlen, basename, x, dtype);
-			if (to_exclude != -1)
-				return to_exclude;
+			const char *new_pathname = pathname;
+			int new_pathlen = pathlen;
+
+			while (1) {
+				int to_exclude = excluded_from_list_1(new_pathname, new_pathlen, basename, x, dtype);
+				if (to_exclude != -1)
+					return to_exclude;
+				if (x->flags & EXC_FLAG_STARSTAR) {
+					const char *p = strchr(new_pathname, '/');
+					if (p) {
+						new_pathlen -= p - new_pathname - 1;
+						new_pathname = p + 1;
+						continue;
+					}
+				}
+				break;
+			}
 		}
 	}
 	return -1; /* undecided */
-- 
1.7.0.2.445.gcbdb3