diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2015-04-17 16:57:26 -0500 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2015-04-17 16:57:26 -0500 |
commit | 4c02d393748d0db382450871ad9ef6898a2ce360 (patch) | |
tree | 8cb574b210cde1d61bfa4b91e62e579761470019 | |
parent | a0e652d281dd2152d333675b25aa37307496e039 (diff) | |
parent | 4f3586034b3db317d360de87bd962de1ef3d524e (diff) | |
download | libgit2-4c02d393748d0db382450871ad9ef6898a2ce360.tar.gz |
Merge pull request #3016 from pks-t/ignore-exclude-fix
ignore: fix negative ignores without wildcards.
-rw-r--r-- | src/ignore.c | 50 | ||||
-rw-r--r-- | tests/status/ignore.c | 53 |
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; |