summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-11-06 10:38:25 -0500
committerEdward Thomson <ethomson@edwardthomson.com>2014-11-06 10:38:25 -0500
commit64dc248577940c7874939bb82b6a557bafe8c388 (patch)
tree9e3c3a06f54d4f73603d5eb5e283b3eba326d724
parent7bb639911b04addddfda86ca7f61398f779b0d7f (diff)
parent6069042fcbf7a555c344fa1f020fac9b72d27aad (diff)
downloadlibgit2-64dc248577940c7874939bb82b6a557bafe8c388.tar.gz
Merge pull request #2598 from libgit2/cmn/stacked-ignore
ignore: don't leak rules into higher directores
-rw-r--r--src/attr_file.c26
-rw-r--r--src/attr_file.h2
-rw-r--r--tests/status/ignore.c32
3 files changed, 60 insertions, 0 deletions
diff --git a/src/attr_file.c b/src/attr_file.c
index 562075291..e3692cee9 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 87cde7e35..93de84d12 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 7cf8803c2..6b31e7785 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);
+}