Commit 21baf7abe7c41f9a96c9dad7a226b2d0877d309d

Patrick Steinhardt 2019-04-05T10:22:46

ignore: treat paths with trailing "/" as directories The function `git_ignore_path_is_ignored` is there to test the ignore status of paths that need not necessarily exist inside of a repository. This has the implication that for a given path, we cannot always decide whether it references a directory or a file, and we need to distinguish those cases because ignore rules may treat those differently. E.g. given the following gitignore file: * !/**/ we'd only want to unignore directories, while keeping files ignored. But still, calling `git_ignore_path_is_ignored("dir/")` will say that this directory is ignored because it treats "dir/" as a file path. As said, the `is_ignored` function cannot always decide whether the given path is a file or directory, and thus it may produce wrong results in some cases. While this is unfixable in the general case, we can do better when we are being passed a path name with a trailing path separator (e.g. "dir/") and always treat them as directories.

diff --git a/src/ignore.c b/src/ignore.c
index c6e4da7..5427efa 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -534,7 +534,9 @@ int git_ignore_path_is_ignored(
 	memset(&path, 0, sizeof(path));
 	memset(&ignores, 0, sizeof(ignores));
 
-	if (git_repository_is_bare(repo))
+	if (!git__suffixcmp(pathname, "/"))
+		dir_flag = GIT_DIR_FLAG_TRUE;
+	else if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
 
 	if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index 110304a..1bf06fc 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -397,3 +397,19 @@ void test_attr_ignore__ignored_subdirfiles_with_negations(void)
 	assert_is_ignored(true, "dir/sub1/c.test");
 }
 
+void test_attr_ignore__negative_directory_rules_only_match_directories(void)
+{
+	cl_git_rewritefile(
+		"attr/.gitignore",
+		"*\n"
+		"!/**/\n"
+		"!*.keep\n"
+		"!.gitignore\n"
+	);
+
+	assert_is_ignored(true, "src");
+	assert_is_ignored(true, "src/A");
+	assert_is_ignored(false, "src/");
+	assert_is_ignored(false, "src/A.keep");
+	assert_is_ignored(false, ".gitignore");
+}