Skip to content

Commit bc0f322

Browse files
authored
Merge pull request libgit2#4670 from pks-t/pks/ignore-leadingdir
Fix negative gitignore rules with leading directories
2 parents 0ef3242 + d22fd81 commit bc0f322

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

src/attr_file.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,9 @@ int git_attr_fnmatch__parse(
594594
}
595595

596596
if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
597-
spec->flags = spec->flags |
598-
GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
597+
spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
598+
if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0)
599+
spec->flags |= GIT_ATTR_FNMATCH_LEADINGDIR;
599600
pattern++;
600601
}
601602

src/ignore.c

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,23 +133,12 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
133133
continue;
134134
}
135135

136-
/*
137-
* When dealing with a directory, we add '/<star>' so
138-
* p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR
139-
* alone isn't enough as that's also set for nagations, so we
140-
* need to check that NEGATIVE is off.
141-
*/
142136
git_buf_clear(&buf);
143-
if (rule->containing_dir) {
137+
if (rule->containing_dir)
144138
git_buf_puts(&buf, rule->containing_dir);
145-
}
146-
147-
error = git_buf_puts(&buf, rule->pattern);
139+
git_buf_puts(&buf, rule->pattern);
148140

149-
if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR)
150-
error = git_buf_PUTS(&buf, "/*");
151-
152-
if (error < 0)
141+
if (git_buf_oom(&buf))
153142
goto out;
154143

155144
if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) {
@@ -203,7 +192,10 @@ static int parse_ignore_file(
203192
break;
204193
}
205194

206-
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
195+
match->flags =
196+
GIT_ATTR_FNMATCH_ALLOWSPACE |
197+
GIT_ATTR_FNMATCH_ALLOWNEG |
198+
GIT_ATTR_FNMATCH_NOLEADINGDIR;
207199

208200
if (!(error = git_attr_fnmatch__parse(
209201
match, &attrs->pool, context, &scan)))
@@ -445,6 +437,9 @@ static bool ignore_lookup_in_rules(
445437
git_attr_fnmatch *match;
446438

447439
git_vector_rforeach(&file->rules, j, match) {
440+
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
441+
path->is_dir == GIT_DIR_FLAG_FALSE)
442+
continue;
448443
if (git_attr_fnmatch__match(match, path)) {
449444
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
450445
GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;

tests/status/ignore.c

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,27 +1158,58 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void)
11581158

11591159
void test_status_ignore__deeper(void)
11601160
{
1161-
int ignored;
1161+
const char *test_files[] = {
1162+
"empty_standard_repo/foo.data",
1163+
"empty_standard_repo/bar.data",
1164+
"empty_standard_repo/dont_ignore/foo.data",
1165+
"empty_standard_repo/dont_ignore/bar.data",
1166+
NULL
1167+
};
11621168

1163-
g_repo = cl_git_sandbox_init("empty_standard_repo");
1169+
make_test_data("empty_standard_repo", test_files);
1170+
cl_git_mkfile("empty_standard_repo/.gitignore",
1171+
"*.data\n"
1172+
"!dont_ignore/*.data\n");
11641173

1165-
cl_git_mkfile("empty_standard_repo/.gitignore",
1166-
"*.data\n"
1167-
"!dont_ignore/*.data\n");
1174+
assert_is_ignored("foo.data");
1175+
assert_is_ignored("bar.data");
11681176

1169-
cl_git_pass(p_mkdir("empty_standard_repo/dont_ignore", 0777));
1170-
cl_git_mkfile("empty_standard_repo/foo.data", "");
1171-
cl_git_mkfile("empty_standard_repo/bar.data", "");
1172-
cl_git_mkfile("empty_standard_repo/dont_ignore/foo.data", "");
1173-
cl_git_mkfile("empty_standard_repo/dont_ignore/bar.data", "");
1177+
refute_is_ignored("dont_ignore/foo.data");
1178+
refute_is_ignored("dont_ignore/bar.data");
1179+
}
11741180

1175-
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "foo.data"));
1176-
cl_assert_equal_i(1, ignored);
1177-
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "bar.data"));
1178-
cl_assert_equal_i(1, ignored);
1181+
void test_status_ignore__unignored_dir_with_ignored_contents(void)
1182+
{
1183+
static const char *test_files[] = {
1184+
"empty_standard_repo/dir/a.test",
1185+
"empty_standard_repo/dir/subdir/a.test",
1186+
NULL
1187+
};
11791188

1180-
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/foo.data"));
1181-
cl_assert_equal_i(0, ignored);
1182-
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/bar.data"));
1183-
cl_assert_equal_i(0, ignored);
1189+
make_test_data("empty_standard_repo", test_files);
1190+
cl_git_mkfile(
1191+
"empty_standard_repo/.gitignore",
1192+
"*.test\n"
1193+
"!dir/*\n");
1194+
1195+
refute_is_ignored("dir/a.test");
1196+
assert_is_ignored("dir/subdir/a.test");
1197+
}
1198+
1199+
void test_status_ignore__unignored_subdirs(void)
1200+
{
1201+
static const char *test_files[] = {
1202+
"empty_standard_repo/dir/a.test",
1203+
"empty_standard_repo/dir/subdir/a.test",
1204+
NULL
1205+
};
1206+
1207+
make_test_data("empty_standard_repo", test_files);
1208+
cl_git_mkfile(
1209+
"empty_standard_repo/.gitignore",
1210+
"dir/*\n"
1211+
"!dir/*/\n");
1212+
1213+
assert_is_ignored("dir/a.test");
1214+
refute_is_ignored("dir/subdir/a.test");
11841215
}

0 commit comments

Comments
 (0)