Commit 1c3018eb12c03010fe0db740bc9e67af4992e594

Carlos Martín Nieto 2016-04-18T13:34:18

ignore: fix directory limits when searching for star-star In order to match the star-star, we disable the flag that's looking for a single path element, but that leads to searching for the pattern in the middle of elements in the input string. Mark when we're handing a star-star so we jump over the elements in our attempt to match the part of the pattern that comes after the star-star. While here, tighten up the check so we don't allow invalid rules through.

diff --git a/src/fnmatch.c b/src/fnmatch.c
index a2945b8..ba1964b 100644
--- a/src/fnmatch.c
+++ b/src/fnmatch.c
@@ -69,7 +69,8 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
 		if (recurs-- == 0)
 				return FNM_NORES;
 
-		for (stringstart = string;;)
+		for (stringstart = string;;) {
+				bool match_slash = false;
 				switch (c = *pattern++) {
 				case EOS:
 						if ((flags & FNM_LEADING_DIR) && *string == '/')
@@ -93,11 +94,17 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
 						 * It will be restored if/when we recurse below.
 						 */
 						if (c == '*') {
+							c = *++pattern;
+							/* star-star-slash is at the end, match by default */
+							if (c == EOS)
+								return 0;
+							/* Double-star must be at end or between slashes */
+							if (c != '/')
+								return (FNM_NOMATCH);
+
+							c = *++pattern;
 							flags &= ~FNM_PATHNAME;
-							while (c == '*')
-								c = *++pattern;
-							if (c == '/')
-								c = *++pattern;
+							match_slash = true;
 						}
 
 						if (*string == '.' && (flags & FNM_PERIOD) &&
@@ -128,7 +135,17 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
 										return e;
 								if (test == '/' && (flags & FNM_PATHNAME))
 										break;
-								++string;
+
+								/* searching for star-star, so we jump over entire dirs */
+								if (match_slash) {
+									const char *slash;
+									if (!(slash = strchr(string, '/')))
+										break;
+
+									string = slash + 1;
+								} else {
+									++string;
+								}
 						}
 						return (FNM_NOMATCH);
 				case '[':
@@ -170,6 +187,7 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
 						++string;
 						break;
 				}
+		}
 		/* NOTREACHED */
 }
 
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index f11dad5..f1fe1c7 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -134,13 +134,25 @@ void test_attr_ignore__leading_stars(void)
 
 void test_attr_ignore__globs_and_path_delimiters(void)
 {
+	cl_git_rewritefile("attr/.gitignore", "foo/bar/**");
+	assert_is_ignored(true, "foo/bar/baz");
+	assert_is_ignored(true, "foo/bar/baz/quux");
+
+	cl_git_rewritefile("attr/.gitignore", "_*/");
+	assert_is_ignored(true, "sub/_test/a/file");
+	assert_is_ignored(false, "test_folder/file");
+	assert_is_ignored(true, "_test/file");
+	assert_is_ignored(true, "_test/a/file");
+
 	cl_git_rewritefile("attr/.gitignore", "**/_*/");
+	assert_is_ignored(true, "sub/_test/a/file");
 	assert_is_ignored(false, "test_folder/file");
 	assert_is_ignored(true, "_test/file");
 	assert_is_ignored(true, "_test/a/file");
 
 	cl_git_rewritefile("attr/.gitignore", "**/_*/foo/bar/*ux");
 
+	assert_is_ignored(true, "sub/_test/foo/bar/qux/file");
 	assert_is_ignored(true, "_test/foo/bar/qux/file");
 	assert_is_ignored(true, "_test/foo/bar/crux/file");
 	assert_is_ignored(false, "_test/foo/bar/code/file");