Commit 64dc248577940c7874939bb82b6a557bafe8c388

Edward Thomson 2014-11-06T10:38:25

Merge pull request #2598 from libgit2/cmn/stacked-ignore ignore: don't leak rules into higher directores

diff --git a/src/attr_file.c b/src/attr_file.c
index 5620752..e3692ce 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -347,6 +347,21 @@ bool git_attr_fnmatch__match(
 	const char *filename;
 	int flags = 0;
 
+	/*
+	 * If the rule was generated in a subdirectory, we must only
+	 * use it for paths inside that directory. We can thus return
+	 * a non-match if the prefixes don't match.
+	 */
+	if (match->containing_dir) {
+		if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
+			if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
+				return 0;
+		} else {
+			if (git__prefixcmp(path->path, match->containing_dir))
+				return 0;
+		}
+	}
+
 	if (match->flags & GIT_ATTR_FNMATCH_ICASE)
 		flags |= FNM_CASEFOLD;
 	if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
@@ -588,6 +603,17 @@ int git_attr_fnmatch__parse(
 		/* leave FULLPATH match on, however */
 	}
 
+	if (context) {
+		char *slash = strchr(context, '/');
+		size_t len;
+		if (slash) {
+			/* include the slash for easier matching */
+			len = slash - context + 1;
+			spec->containing_dir = git_pool_strndup(pool, context, len);
+			spec->containing_dir_length = len;
+		}
+	}
+
 	if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
 		context != NULL && git_path_root(pattern) < 0)
 	{
diff --git a/src/attr_file.h b/src/attr_file.h
index 87cde7e..93de84d 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -52,6 +52,8 @@ extern const char *git_attr__unset;
 typedef struct {
 	char *pattern;
 	size_t length;
+	char *containing_dir;
+	size_t containing_dir_length;
 	unsigned int flags;
 } git_attr_fnmatch;
 
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index 7cf8803..6b31e77 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -915,3 +915,35 @@ void test_status_ignore__filename_with_cr(void)
 	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon"));
 	cl_assert_equal_i(1, ignored);
 }
+
+void test_status_ignore__subdir_doesnt_match_above(void)
+{
+	int ignored, icase = 0, error;
+	git_config *cfg;
+
+	g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+	cl_git_pass(git_repository_config_snapshot(&cfg, g_repo));
+	error = git_config_get_bool(&icase, cfg, "core.ignorecase");
+	if (error == GIT_ENOTFOUND)
+		error = 0;
+
+	cl_git_pass(error);
+
+	cl_git_pass(p_mkdir("empty_standard_repo/src", 0777));
+	cl_git_pass(p_mkdir("empty_standard_repo/src/src", 0777));
+	cl_git_mkfile("empty_standard_repo/src/.gitignore", "src\n");
+	cl_git_mkfile("empty_standard_repo/.gitignore", "");
+
+	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/test.txt"));
+	cl_assert_equal_i(0, ignored);
+	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/src/test.txt"));
+	cl_assert_equal_i(1, ignored);
+	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/foo/test.txt"));
+	cl_assert_equal_i(0, ignored);
+
+	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "SRC/src/test.txt"));
+	cl_assert_equal_i(icase, ignored);
+	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/SRC/test.txt"));
+	cl_assert_equal_i(icase, ignored);
+}