Skip to content

Commit 2d9ff8f

Browse files
committed
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.
1 parent 38b44c3 commit 2d9ff8f

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

src/ignore.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,22 @@
4848
*/
4949
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
5050
{
51+
int (*cmp)(const char *, const char *, size_t);
5152
git_attr_fnmatch *longer, *shorter;
5253
char *p;
5354

5455
if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0
5556
|| (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
5657
return false;
5758

59+
if (neg->flags & GIT_ATTR_FNMATCH_ICASE)
60+
cmp = git__strncasecmp;
61+
else
62+
cmp = strncmp;
63+
5864
/* If lengths match we need to have an exact match */
5965
if (rule->length == neg->length) {
60-
return strcmp(rule->pattern, neg->pattern) == 0;
66+
return cmp(rule->pattern, neg->pattern, rule->length) == 0;
6167
} else if (rule->length < neg->length) {
6268
shorter = rule;
6369
longer = neg;
@@ -77,7 +83,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
7783
if (memchr(shorter->pattern, '/', shorter->length) != NULL)
7884
return false;
7985

80-
return memcmp(p, shorter->pattern, shorter->length) == 0;
86+
return cmp(p, shorter->pattern, shorter->length) == 0;
8187
}
8288

8389
/**
@@ -95,14 +101,18 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
95101
*/
96102
static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
97103
{
98-
int error = 0;
104+
int error = 0, fnflags;
99105
size_t i;
100106
git_attr_fnmatch *rule;
101107
char *path;
102108
git_buf buf = GIT_BUF_INIT;
103109

104110
*out = 0;
105111

112+
fnflags = FNM_PATHNAME;
113+
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
114+
fnflags |= FNM_IGNORECASE;
115+
106116
/* path of the file relative to the workdir, so we match the rules in subdirs */
107117
if (match->containing_dir) {
108118
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
142152
if (error < 0)
143153
goto out;
144154

145-
if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) {
155+
if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) {
146156
giterr_set(GITERR_INVALID, "error matching pattern");
147157
goto out;
148158
}

tests/attr/ignore.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,37 @@ void test_attr_ignore__unignore_dir_succeeds(void)
312312
assert_is_ignored(false, "src/foo.c");
313313
assert_is_ignored(true, "src/foo/foo.c");
314314
}
315+
316+
void test_attr_ignore__case_insensitive_unignores_previous_rule(void)
317+
{
318+
git_config *cfg;
319+
320+
cl_git_rewritefile("attr/.gitignore",
321+
"/case\n"
322+
"!/Case/\n");
323+
324+
cl_git_pass(git_repository_config(&cfg, g_repo));
325+
cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", true));
326+
327+
cl_must_pass(p_mkdir("attr/case", 0755));
328+
cl_git_mkfile("attr/case/file", "content");
329+
330+
assert_is_ignored(false, "case/file");
331+
}
332+
333+
void test_attr_ignore__case_sensitive_unignore_does_nothing(void)
334+
{
335+
git_config *cfg;
336+
337+
cl_git_rewritefile("attr/.gitignore",
338+
"/case\n"
339+
"!/Case/\n");
340+
341+
cl_git_pass(git_repository_config(&cfg, g_repo));
342+
cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", false));
343+
344+
cl_must_pass(p_mkdir("attr/case", 0755));
345+
cl_git_mkfile("attr/case/file", "content");
346+
347+
assert_is_ignored(true, "case/file");
348+
}

0 commit comments

Comments
 (0)