Commit 2d9ff8f5 by Patrick Steinhardt

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.
parent 38b44c3b
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
*/ */
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) 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; git_attr_fnmatch *longer, *shorter;
char *p; char *p;
...@@ -55,9 +56,14 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) ...@@ -55,9 +56,14 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
|| (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
return false; 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 lengths match we need to have an exact match */
if (rule->length == neg->length) { 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) { } else if (rule->length < neg->length) {
shorter = rule; shorter = rule;
longer = neg; longer = neg;
...@@ -77,7 +83,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *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) if (memchr(shorter->pattern, '/', shorter->length) != NULL)
return false; 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) ...@@ -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) static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
{ {
int error = 0; int error = 0, fnflags;
size_t i; size_t i;
git_attr_fnmatch *rule; git_attr_fnmatch *rule;
char *path; char *path;
...@@ -103,6 +109,10 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match ...@@ -103,6 +109,10 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
*out = 0; *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 */ /* path of the file relative to the workdir, so we match the rules in subdirs */
if (match->containing_dir) { if (match->containing_dir) {
git_buf_puts(&buf, 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 ...@@ -142,7 +152,7 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
if (error < 0) if (error < 0)
goto out; 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"); giterr_set(GITERR_INVALID, "error matching pattern");
goto out; goto out;
} }
......
...@@ -312,3 +312,37 @@ void test_attr_ignore__unignore_dir_succeeds(void) ...@@ -312,3 +312,37 @@ void test_attr_ignore__unignore_dir_succeeds(void)
assert_is_ignored(false, "src/foo.c"); assert_is_ignored(false, "src/foo.c");
assert_is_ignored(true, "src/foo/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");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment