summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2015-03-24 16:33:50 +0100
committerPatrick Steinhardt <ps@pks.im>2015-04-17 09:59:16 +0200
commit4f3586034b3db317d360de87bd962de1ef3d524e (patch)
tree7a531778b1c5758b4fd01f21ea7a380377bc42c2
parent2a0f67f04cb717a7e57192696d69f91a3d208705 (diff)
downloadlibgit2-4f3586034b3db317d360de87bd962de1ef3d524e.tar.gz
ignore: fix negative ignores without wildcards.
-rw-r--r--src/ignore.c50
-rw-r--r--tests/status/ignore.c53
2 files changed, 98 insertions, 5 deletions
diff --git a/src/ignore.c b/src/ignore.c
index dd299f076..3a5efedce 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -11,6 +11,41 @@
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
/**
+ * A negative ignore pattern can match a positive one without
+ * wildcards if its pattern equals the tail of the positive
+ * pattern. Thus
+ *
+ * foo/bar
+ * !bar
+ *
+ * would result in foo/bar being unignored again.
+ */
+static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
+{
+ char *p;
+
+ if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0
+ && (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) {
+ /*
+ * no chance of matching if rule is shorter than
+ * the negated one
+ */
+ if (rule->length < neg->length)
+ return false;
+
+ /*
+ * shift pattern so its tail aligns with the
+ * negated pattern
+ */
+ p = rule->pattern + rule->length - neg->length;
+ if (strcmp(p, neg->pattern) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+/**
* A negative ignore can only unignore a file which is given explicitly before, thus
*
* foo
@@ -31,6 +66,8 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
char *path;
git_buf buf = GIT_BUF_INIT;
+ *out = 0;
+
/* path of the file relative to the workdir, so we match the rules in subdirs */
if (match->containing_dir) {
git_buf_puts(&buf, match->containing_dir);
@@ -41,9 +78,14 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
path = git_buf_detach(&buf);
git_vector_foreach(rules, i, rule) {
- /* no chance of matching w/o a wilcard */
- if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD))
- continue;
+ if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
+ if (does_negate_pattern(rule, match)) {
+ *out = 1;
+ goto out;
+ }
+ else
+ continue;
+ }
/*
* If we're dealing with a directory (which we know via the
@@ -62,7 +104,6 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
if (error < 0)
goto out;
-
if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) {
giterr_set(GITERR_INVALID, "error matching pattern");
goto out;
@@ -76,7 +117,6 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
}
}
- *out = 0;
error = 0;
out:
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index a15b11d1d..3193d318e 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -892,6 +892,59 @@ void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores(
cl_assert(found_parent_child2_file);
}
+void test_status_ignore__negative_directory_ignores(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/parent/child1/bar.txt",
+ "empty_standard_repo/parent/child2/bar.txt",
+ "empty_standard_repo/parent/child3/foo.txt",
+ "empty_standard_repo/parent/child4/bar.txt",
+ "empty_standard_repo/parent/nested/child5/bar.txt",
+ "empty_standard_repo/parent/nested/child6/bar.txt",
+ "empty_standard_repo/parent/nested/child7/bar.txt",
+ "empty_standard_repo/padded_parent/child8/bar.txt",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "foo.txt\n"
+ "parent/child1\n"
+ "parent/child2\n"
+ "parent/child4\n"
+ "parent/nested/child5\n"
+ "nested/child6\n"
+ "nested/child7\n"
+ "padded_parent/child8\n"
+ /* test simple exact match */
+ "!parent/child1\n"
+ /* test negating file without negating dir */
+ "!parent/child2/bar.txt\n"
+ /* test negative pattern on dir with its content
+ * being ignored */
+ "!parent/child3\n"
+ /* test with partial match at end */
+ "!child4\n"
+ /* test with partial match with '/' at end */
+ "!nested/child5\n"
+ /* test with complete match */
+ "!nested/child6\n"
+ /* test with trailing '/' */
+ "!child7/\n"
+ /* test with partial dir match */
+ "!_parent/child8\n");
+
+ refute_is_ignored("parent/child1/bar.txt");
+ assert_is_ignored("parent/child2/bar.txt");
+ assert_is_ignored("parent/child3/foo.txt");
+ refute_is_ignored("parent/child4/bar.txt");
+ assert_is_ignored("parent/nested/child5/bar.txt");
+ refute_is_ignored("parent/nested/child6/bar.txt");
+ refute_is_ignored("parent/nested/child7/bar.txt");
+ assert_is_ignored("padded_parent/child8/bar.txt");
+}
+
void test_status_ignore__filename_with_cr(void)
{
int ignored;