Merge pull request #4670 from pks-t/pks/ignore-leadingdir Fix negative gitignore rules with leading directories
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
diff --git a/src/attr_file.c b/src/attr_file.c
index f46cce3..6821587 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -594,8 +594,9 @@ int git_attr_fnmatch__parse(
}
if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
- spec->flags = spec->flags |
- GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
+ if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0)
+ spec->flags |= GIT_ATTR_FNMATCH_LEADINGDIR;
pattern++;
}
diff --git a/src/ignore.c b/src/ignore.c
index 76b9972..3b68e14 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -133,23 +133,12 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
continue;
}
- /*
- * When dealing with a directory, we add '/<star>' so
- * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR
- * alone isn't enough as that's also set for nagations, so we
- * need to check that NEGATIVE is off.
- */
git_buf_clear(&buf);
- if (rule->containing_dir) {
+ if (rule->containing_dir)
git_buf_puts(&buf, rule->containing_dir);
- }
-
- error = git_buf_puts(&buf, rule->pattern);
+ git_buf_puts(&buf, rule->pattern);
- if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR)
- error = git_buf_PUTS(&buf, "/*");
-
- if (error < 0)
+ if (git_buf_oom(&buf))
goto out;
if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) {
@@ -203,7 +192,10 @@ static int parse_ignore_file(
break;
}
- match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
+ match->flags =
+ GIT_ATTR_FNMATCH_ALLOWSPACE |
+ GIT_ATTR_FNMATCH_ALLOWNEG |
+ GIT_ATTR_FNMATCH_NOLEADINGDIR;
if (!(error = git_attr_fnmatch__parse(
match, &attrs->pool, context, &scan)))
@@ -445,6 +437,9 @@ static bool ignore_lookup_in_rules(
git_attr_fnmatch *match;
git_vector_rforeach(&file->rules, j, match) {
+ if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
+ path->is_dir == GIT_DIR_FLAG_FALSE)
+ continue;
if (git_attr_fnmatch__match(match, path)) {
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index dc58e8b..824475e 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -1158,27 +1158,58 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void)
void test_status_ignore__deeper(void)
{
- int ignored;
+ const char *test_files[] = {
+ "empty_standard_repo/foo.data",
+ "empty_standard_repo/bar.data",
+ "empty_standard_repo/dont_ignore/foo.data",
+ "empty_standard_repo/dont_ignore/bar.data",
+ NULL
+ };
- g_repo = cl_git_sandbox_init("empty_standard_repo");
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile("empty_standard_repo/.gitignore",
+ "*.data\n"
+ "!dont_ignore/*.data\n");
- cl_git_mkfile("empty_standard_repo/.gitignore",
- "*.data\n"
- "!dont_ignore/*.data\n");
+ assert_is_ignored("foo.data");
+ assert_is_ignored("bar.data");
- cl_git_pass(p_mkdir("empty_standard_repo/dont_ignore", 0777));
- cl_git_mkfile("empty_standard_repo/foo.data", "");
- cl_git_mkfile("empty_standard_repo/bar.data", "");
- cl_git_mkfile("empty_standard_repo/dont_ignore/foo.data", "");
- cl_git_mkfile("empty_standard_repo/dont_ignore/bar.data", "");
+ refute_is_ignored("dont_ignore/foo.data");
+ refute_is_ignored("dont_ignore/bar.data");
+}
- cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "foo.data"));
- cl_assert_equal_i(1, ignored);
- cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "bar.data"));
- cl_assert_equal_i(1, ignored);
+void test_status_ignore__unignored_dir_with_ignored_contents(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/dir/a.test",
+ "empty_standard_repo/dir/subdir/a.test",
+ NULL
+ };
- cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/foo.data"));
- cl_assert_equal_i(0, ignored);
- cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/bar.data"));
- cl_assert_equal_i(0, ignored);
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "*.test\n"
+ "!dir/*\n");
+
+ refute_is_ignored("dir/a.test");
+ assert_is_ignored("dir/subdir/a.test");
+}
+
+void test_status_ignore__unignored_subdirs(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/dir/a.test",
+ "empty_standard_repo/dir/subdir/a.test",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "dir/*\n"
+ "!dir/*/\n");
+
+ assert_is_ignored("dir/a.test");
+ refute_is_ignored("dir/subdir/a.test");
}