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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
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");