summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2014-04-18 10:32:35 -0700
committerRussell Belfer <rb@github.com>2014-04-18 10:32:35 -0700
commit6a0956e504328f6584af971e840c202ecb21b5b6 (patch)
tree234b3fc25eecccc41a6cafe7c7322eb476b67569
parent3c69bebc1c18444e9358c33f56c7cfefea4d1a8f (diff)
downloadlibgit2-6a0956e504328f6584af971e840c202ecb21b5b6.tar.gz
Pop ignore only if whole relative path matches
When traversing the directory structure, the iterator pushes and pops ignore files using a vector. Some directories don't have ignore files, so it uses a path comparison to decide when it is right to actually pop the last ignore file. This was only comparing directory suffixes, though, so a subdirectory with the same name as a parent could result in the parent's .gitignore being popped off the list ignores too early. This changes the logic to compare the entire relative path of the ignore file.
-rw-r--r--src/ignore.c13
-rw-r--r--src/ignore.h1
-rw-r--r--src/path.c2
-rw-r--r--tests/status/ignore.c86
-rw-r--r--tests/status/status_helpers.c13
5 files changed, 80 insertions, 35 deletions
diff --git a/src/ignore.c b/src/ignore.c
index deae204f8..b08ff2200 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -123,7 +123,7 @@ int git_ignore__for_path(
int error = 0;
const char *workdir = git_repository_workdir(repo);
- assert(ignores);
+ assert(ignores && path);
memset(ignores, 0, sizeof(*ignores));
ignores->repo = repo;
@@ -140,10 +140,13 @@ int git_ignore__for_path(
if (workdir && git_path_root(path) < 0)
error = git_path_find_dir(&ignores->dir, path, workdir);
else
- error = git_buf_sets(&ignores->dir, path);
+ error = git_buf_joinpath(&ignores->dir, path, "");
if (error < 0)
goto cleanup;
+ if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
+ ignores->dir_root = strlen(workdir);
+
/* set up internals */
if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
goto cleanup;
@@ -204,10 +207,10 @@ int git_ignore__pop_dir(git_ignores *ign)
if ((end = strrchr(start, '/')) != NULL) {
size_t dirlen = (end - start) + 1;
+ const char *relpath = ign->dir.ptr + ign->dir_root;
+ size_t pathlen = ign->dir.size - ign->dir_root;
- if (ign->dir.size >= dirlen &&
- !memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
- {
+ if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
git_vector_pop(&ign->ign_path);
git_attr_file__free(file);
}
diff --git a/src/ignore.h b/src/ignore.h
index 46172c72f..ff9369000 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -28,6 +28,7 @@ typedef struct {
git_attr_file *ign_internal;
git_vector ign_path;
git_vector ign_global;
+ size_t dir_root; /* offset in dir to repo root */
int ignore_case;
int depth;
} git_ignores;
diff --git a/src/path.c b/src/path.c
index 7cad28d45..a990b005f 100644
--- a/src/path.c
+++ b/src/path.c
@@ -624,7 +624,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base)
/* call dirname if this is not a directory */
if (!error) /* && git_path_isdir(dir->ptr) == false) */
- error = git_path_dirname_r(dir, dir->ptr);
+ error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
if (!error)
error = git_path_to_dir(dir);
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index 052a8eae8..eb171deb6 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -230,32 +230,34 @@ void test_status_ignore__subdirectories(void)
cl_assert(ignored);
}
-static void make_test_data(void)
+static void make_test_data(const char *reponame, const char **files)
{
- static const char *files[] = {
- "empty_standard_repo/dir/a/ignore_me",
- "empty_standard_repo/dir/b/ignore_me",
- "empty_standard_repo/dir/ignore_me",
- "empty_standard_repo/ignore_also/file",
- "empty_standard_repo/ignore_me",
- "empty_standard_repo/test/ignore_me/file",
- "empty_standard_repo/test/ignore_me/file2",
- "empty_standard_repo/test/ignore_me/and_me/file",
- NULL
- };
- static const char *repo = "empty_standard_repo";
const char **scan;
- size_t repolen = strlen(repo) + 1;
+ size_t repolen = strlen(reponame) + 1;
- g_repo = cl_git_sandbox_init(repo);
+ g_repo = cl_git_sandbox_init(reponame);
for (scan = files; *scan != NULL; ++scan) {
cl_git_pass(git_futils_mkdir(
- *scan + repolen, repo, 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST));
+ *scan + repolen, reponame,
+ 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST));
cl_git_mkfile(*scan, "contents");
}
}
+static const char *test_repo_1 = "empty_standard_repo";
+static const char *test_files_1[] = {
+ "empty_standard_repo/dir/a/ignore_me",
+ "empty_standard_repo/dir/b/ignore_me",
+ "empty_standard_repo/dir/ignore_me",
+ "empty_standard_repo/ignore_also/file",
+ "empty_standard_repo/ignore_me",
+ "empty_standard_repo/test/ignore_me/file",
+ "empty_standard_repo/test/ignore_me/file2",
+ "empty_standard_repo/test/ignore_me/and_me/file",
+ NULL
+};
+
void test_status_ignore__subdirectories_recursion(void)
{
/* Let's try again with recursing into ignored dirs turned on */
@@ -292,7 +294,7 @@ void test_status_ignore__subdirectories_recursion(void)
GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
};
- make_test_data();
+ make_test_data(test_repo_1, test_files_1);
cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n");
memset(&counts, 0x0, sizeof(status_entry_counts));
@@ -347,7 +349,7 @@ void test_status_ignore__subdirectories_not_at_root(void)
GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
};
- make_test_data();
+ make_test_data(test_repo_1, test_files_1);
cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "ignore_me\n/ignore_also\n");
cl_git_rewritefile("empty_standard_repo/test/.gitignore", "and_me\n");
@@ -389,7 +391,7 @@ void test_status_ignore__leading_slash_ignores(void)
GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
};
- make_test_data();
+ make_test_data(test_repo_1, test_files_1);
cl_fake_home(&home);
cl_git_mkfile("home/.gitignore", "/ignore_me\n");
@@ -422,6 +424,52 @@ void test_status_ignore__leading_slash_ignores(void)
cl_fake_home_cleanup(&home);
}
+void test_status_ignore__contained_dir_with_matching_name(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/subdir_match/aaa/subdir_match/file",
+ "empty_standard_repo/subdir_match/zzz_ignoreme",
+ NULL
+ };
+ static const char *expected_paths[] = {
+ "subdir_match/.gitignore",
+ "subdir_match/aaa/subdir_match/file",
+ "subdir_match/zzz_ignoreme",
+ };
+ static const unsigned int expected_statuses[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED
+ };
+ int ignored;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/subdir_match/.gitignore", "*_ignoreme\n");
+
+ cl_git_pass(git_status_should_ignore(
+ &ignored, g_repo, "subdir_match/aaa/subdir_match/file"));
+ cl_assert(!ignored);
+
+ cl_git_pass(git_status_should_ignore(
+ &ignored, g_repo, "subdir_match/zzz_ignoreme"));
+ cl_assert(ignored);
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 3;
+ counts.expected_paths = expected_paths;
+ counts.expected_statuses = expected_statuses;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
void test_status_ignore__adding_internal_ignores(void)
{
int ignored;
diff --git a/tests/status/status_helpers.c b/tests/status/status_helpers.c
index 902b65c4f..088279252 100644
--- a/tests/status/status_helpers.c
+++ b/tests/status/status_helpers.c
@@ -9,20 +9,13 @@ int cb_status__normal(
if (counts->debug)
cb_status__print(path, status_flags, NULL);
- if (counts->entry_count >= counts->expected_entry_count) {
+ if (counts->entry_count >= counts->expected_entry_count)
counts->wrong_status_flags_count++;
- goto exit;
- }
-
- if (strcmp(path, counts->expected_paths[counts->entry_count])) {
+ else if (strcmp(path, counts->expected_paths[counts->entry_count]))
counts->wrong_sorted_path++;
- goto exit;
- }
-
- if (status_flags != counts->expected_statuses[counts->entry_count])
+ else if (status_flags != counts->expected_statuses[counts->entry_count])
counts->wrong_status_flags_count++;
-exit:
counts->entry_count++;
return 0;
}