diff options
author | Patrick Steinhardt <ps@pks.im> | 2017-07-10 09:36:19 +0200 |
---|---|---|
committer | Patrick Steinhardt <ps@pks.im> | 2017-08-25 18:00:34 +0200 |
commit | 2d9ff8f5dc39ccb44a09365d19e216dff7eb3736 (patch) | |
tree | 864db6c50001a517623edd266be9ef38a7def6ff | |
parent | 38b44c3b360e9831178f2a8b21afbd6d1b6990df (diff) | |
download | libgit2-2d9ff8f5dc39ccb44a09365d19e216dff7eb3736.tar.gz |
ignore: honor case insensitivity for negative ignores
When computing negative ignores, we throw away any rule which does not
undo a previous rule to optimize. But on case insensitive file systems,
we need to keep in mind that a negative ignore can also undo a previous
rule with different case, which we did not yet honor while determining
whether a rule undoes a previous one. So in the following example, we
fail to unignore the "/Case" directory:
/case
!/Case
Make both paths checking whether a plain- or wildcard-based rule undo a
previous rule aware of case-insensitivity. This fixes the described
issue.
-rw-r--r-- | src/ignore.c | 18 | ||||
-rw-r--r-- | tests/attr/ignore.c | 34 |
2 files changed, 48 insertions, 4 deletions
diff --git a/src/ignore.c b/src/ignore.c index 61a94f33a..f089dbeb5 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -48,6 +48,7 @@ */ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) { + int (*cmp)(const char *, const char *, size_t); git_attr_fnmatch *longer, *shorter; char *p; @@ -55,9 +56,14 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) return false; + if (neg->flags & GIT_ATTR_FNMATCH_ICASE) + cmp = git__strncasecmp; + else + cmp = strncmp; + /* If lengths match we need to have an exact match */ if (rule->length == neg->length) { - return strcmp(rule->pattern, neg->pattern) == 0; + return cmp(rule->pattern, neg->pattern, rule->length) == 0; } else if (rule->length < neg->length) { shorter = rule; longer = neg; @@ -77,7 +83,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) if (memchr(shorter->pattern, '/', shorter->length) != NULL) return false; - return memcmp(p, shorter->pattern, shorter->length) == 0; + return cmp(p, shorter->pattern, shorter->length) == 0; } /** @@ -95,7 +101,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) */ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) { - int error = 0; + int error = 0, fnflags; size_t i; git_attr_fnmatch *rule; char *path; @@ -103,6 +109,10 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match *out = 0; + fnflags = FNM_PATHNAME; + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + fnflags |= FNM_IGNORECASE; + /* 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); @@ -142,7 +152,7 @@ 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) { + if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) { giterr_set(GITERR_INVALID, "error matching pattern"); goto out; } diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c index 5adfaf557..856e61f90 100644 --- a/tests/attr/ignore.c +++ b/tests/attr/ignore.c @@ -312,3 +312,37 @@ void test_attr_ignore__unignore_dir_succeeds(void) assert_is_ignored(false, "src/foo.c"); assert_is_ignored(true, "src/foo/foo.c"); } + +void test_attr_ignore__case_insensitive_unignores_previous_rule(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", true)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(false, "case/file"); +} + +void test_attr_ignore__case_sensitive_unignore_does_nothing(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", false)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(true, "case/file"); +} |