Commit 5c15cd949413ad0126c3b1978280319003d6f668

Patrick Steinhardt 2017-07-07T13:27:27

ignore: keep negative rules containing wildcards Ignore rules allow for reverting a previously ignored rule by prefixing it with an exclamation mark. As such, a negative rule can only override previously ignored files. While computing all ignore patterns, we try to use this fact to optimize away some negative rules which do not override any previous patterns, as they won't change the outcome anyway. In some cases, though, this optimization causes us to get the actual ignores wrong for some files. This may happen whenever the pattern contains a wildcard, as we are unable to reason about whether a pattern overrides a previous pattern in a sane way. This happens for example in the case where a gitignore file contains "*.c" and "!src/*.c", where we wouldn't un-ignore files inside of the "src/" subdirectory. In this case, the first solution coming to mind may be to just strip the "src/" prefix and simply compare the basenames. While that would work here, it would stop working as soon as the basename pattern itself is different, like for example with "*x.c" and "!src/*.c. As such, we settle for the easier fix of just not optimizing away rules that contain a wildcard.

diff --git a/src/ignore.c b/src/ignore.c
index a3cae8c..e603ac8 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -197,8 +197,14 @@ static int parse_ignore_file(
 
 			scan = git__next_line(scan);
 
-			/* if a negative match doesn't actually do anything, throw it away */
-			if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE)
+			/*
+			 * If a negative match doesn't actually do anything,
+			 * throw it away. As we cannot always verify whether a
+			 * rule containing wildcards negates another rule, we
+			 * do not optimize away these rules, though.
+			 * */
+			if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE
+			    && !(match->flags & GIT_ATTR_FNMATCH_HASWILD))
 				error = does_negate_rule(&valid_rule, &attrs->rules, match);
 
 			if (!error && valid_rule)
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index a089ee4..5adfaf5 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -303,3 +303,12 @@ void test_attr_ignore__test(void)
 	assert_is_ignored(true, "dist/foo.o");
 	assert_is_ignored(true, "bin/foo");
 }
+
+void test_attr_ignore__unignore_dir_succeeds(void)
+{
+	cl_git_rewritefile("attr/.gitignore",
+		"*.c\n"
+		"!src/*.c\n");
+	assert_is_ignored(false, "src/foo.c");
+	assert_is_ignored(true, "src/foo/foo.c");
+}