Commit 95fbc81dafd64400d51637a27ecd49de5ea63145

Edward Thomson 2016-04-19T15:24:14

Merge pull request #3745 from libgit2/cmn/ignore-starstar Improve star-star matching

diff --git a/src/fnmatch.c b/src/fnmatch.c
index a2945b8..33c8a25 100644
--- a/src/fnmatch.c
+++ b/src/fnmatch.c
@@ -93,11 +93,24 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
 						 * It will be restored if/when we recurse below.
 						 */
 						if (c == '*') {
-							flags &= ~FNM_PATHNAME;
-							while (c == '*')
-								c = *++pattern;
-							if (c == '/')
-								c = *++pattern;
+							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;
+							do {
+								int e = p_fnmatchx(pattern, string, recurs_flags, recurs);
+								if (e != FNM_NOMATCH)
+									return e;
+								string = strchr(string, '/');
+							} while (string++);
+
+							/* If we get here, we didn't find a match */
+							return FNM_NOMATCH;
 						}
 
 						if (*string == '.' && (flags & FNM_PERIOD) &&
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index 91bf984..f1fe1c7 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -132,6 +132,32 @@ void test_attr_ignore__leading_stars(void)
 	assert_is_ignored(false, "dir1/kid2/file");
 }
 
+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");
+}
+
 void test_attr_ignore__skip_gitignore_directory(void)
 {
 	cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder");