mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 06:42:21 +00:00

This started out trying to look at the problems from issue #1425 and gradually grew to a broader set of fixes. There are two core things fixed here: 1. When you had an ignore like "/bin" which is rooted at the top of your tree, instead of immediately adding the "bin/" entry as an ignored item in the diff, we were returning all of the direct descendants of the directory as ignored items. This changes things to immediately ignore the directory. Note that this effects the behavior in test_status_ignore__subdirectories so that we no longer exactly match core gits ignore behavior, but the new behavior probably makes more sense (i.e. we now will include an ignored directory inside an untracked directory that we previously would have left off). 2. When a submodule only contained working directory changes, the diff code was always considering it unmodified which was just an outright bug. The HEAD SHA of the submodule matches the SHA in the parent repo index, and since the SHAs matches, the diff code was overwriting the actual status with UNMODIFIED. These fixes broke existing tests test_diff_workdir__submodules and test_status_ignore__subdirectories but looking it over, I actually think the new results are correct and the old results were wrong. @nulltoken had actually commented on the subdirectory ignore issue previously. I also included in the tests some debugging versions of the shared iteration callback routines that print status or diff information. These aren't used actively in the tests, but can be quickly swapped in to test code to give a better picture of what is being scanned in some of the complex test scenarios.
462 lines
14 KiB
C
462 lines
14 KiB
C
#include "clar_libgit2.h"
|
|
#include "fileops.h"
|
|
#include "git2/attr.h"
|
|
#include "ignore.h"
|
|
#include "attr.h"
|
|
#include "status_helpers.h"
|
|
|
|
static git_repository *g_repo = NULL;
|
|
|
|
void test_status_ignore__initialize(void)
|
|
{
|
|
}
|
|
|
|
void test_status_ignore__cleanup(void)
|
|
{
|
|
cl_git_sandbox_cleanup();
|
|
}
|
|
|
|
void test_status_ignore__0(void)
|
|
{
|
|
struct {
|
|
const char *path;
|
|
int expected;
|
|
} test_cases[] = {
|
|
/* pattern "ign" from .gitignore */
|
|
{ "file", 0 },
|
|
{ "ign", 1 },
|
|
{ "sub", 0 },
|
|
{ "sub/file", 0 },
|
|
{ "sub/ign", 1 },
|
|
{ "sub/ign/file", 1 },
|
|
{ "sub/ign/sub", 1 },
|
|
{ "sub/ign/sub/file", 1 },
|
|
{ "sub/sub", 0 },
|
|
{ "sub/sub/file", 0 },
|
|
{ "sub/sub/ign", 1 },
|
|
{ "sub/sub/sub", 0 },
|
|
/* pattern "dir/" from .gitignore */
|
|
{ "dir", 1 },
|
|
{ "dir/", 1 },
|
|
{ "sub/dir", 1 },
|
|
{ "sub/dir/", 1 },
|
|
{ "sub/dir/file", 1 }, /* contained in ignored parent */
|
|
{ "sub/sub/dir", 0 }, /* dir is not actually a dir, but a file */
|
|
{ NULL, 0 }
|
|
}, *one_test;
|
|
|
|
g_repo = cl_git_sandbox_init("attr");
|
|
|
|
for (one_test = test_cases; one_test->path != NULL; one_test++) {
|
|
int ignored;
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, one_test->path));
|
|
cl_assert_(ignored == one_test->expected, one_test->path);
|
|
}
|
|
|
|
/* confirm that ignore files were cached */
|
|
cl_assert(git_attr_cache__is_cached(g_repo, 0, ".git/info/exclude"));
|
|
cl_assert(git_attr_cache__is_cached(g_repo, 0, ".gitignore"));
|
|
}
|
|
|
|
|
|
void test_status_ignore__1(void)
|
|
{
|
|
int ignored;
|
|
|
|
g_repo = cl_git_sandbox_init("attr");
|
|
|
|
cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n");
|
|
git_attr_cache_flush(g_repo);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "root_test4.txt"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/subdir_test2.txt"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "dir"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "dir/"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/dir"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/dir/"));
|
|
cl_assert(!ignored);
|
|
}
|
|
|
|
|
|
void test_status_ignore__empty_repo_with_gitignore_rewrite(void)
|
|
{
|
|
status_entry_single st;
|
|
int ignored;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/look-ma.txt", "I'm going to be ignored!");
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
|
cl_assert(st.count == 1);
|
|
cl_assert(st.status == GIT_STATUS_WT_NEW);
|
|
|
|
cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
|
|
cl_assert(st.status == GIT_STATUS_WT_NEW);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "*.nomatch\n");
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
|
cl_assert(st.count == 2);
|
|
cl_assert(st.status == GIT_STATUS_WT_NEW);
|
|
|
|
cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
|
|
cl_assert(st.status == GIT_STATUS_WT_NEW);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "*.txt\n");
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
|
cl_assert(st.count == 2);
|
|
cl_assert(st.status == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
|
|
cl_assert(st.status == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt"));
|
|
cl_assert(ignored);
|
|
}
|
|
|
|
void test_status_ignore__ignore_pattern_contains_space(void)
|
|
{
|
|
unsigned int flags;
|
|
const mode_t mode = 0777;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n");
|
|
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/foo bar.txt", "I'm going to be ignored!");
|
|
|
|
cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt"));
|
|
cl_assert(flags == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode));
|
|
cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!");
|
|
|
|
cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt"));
|
|
cl_assert(flags == GIT_STATUS_WT_NEW);
|
|
}
|
|
|
|
void test_status_ignore__ignore_pattern_ignorecase(void)
|
|
{
|
|
unsigned int flags;
|
|
bool ignore_case;
|
|
git_index *index;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "a.txt\n");
|
|
|
|
cl_git_mkfile("empty_standard_repo/A.txt", "Differs in case");
|
|
|
|
cl_git_pass(git_repository_index(&index, g_repo));
|
|
ignore_case = index->ignore_case;
|
|
git_index_free(index);
|
|
|
|
cl_git_pass(git_status_file(&flags, g_repo, "A.txt"));
|
|
cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW);
|
|
}
|
|
|
|
void test_status_ignore__subdirectories(void)
|
|
{
|
|
status_entry_single st;
|
|
int ignored;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/ignore_me", "I'm going to be ignored!");
|
|
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n");
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
|
cl_assert_equal_i(2, st.count);
|
|
cl_assert(st.status == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(git_status_file(&st.status, g_repo, "ignore_me"));
|
|
cl_assert(st.status == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "ignore_me"));
|
|
cl_assert(ignored);
|
|
|
|
/* I've changed libgit2 so that the behavior here now differs from
|
|
* core git but seems to make more sense. In core git, the following
|
|
* items are skipped completed, even if --ignored is passed to status.
|
|
* It you mirror these steps and run "git status -uall --ignored" then
|
|
* you will not see "test/ignore_me/" in the results.
|
|
*
|
|
* However, we had a couple reports of this as a bug, plus there is a
|
|
* similar circumstance where we were differing for core git when you
|
|
* used a rooted path for an ignore, so I changed this behavior.
|
|
*/
|
|
cl_git_pass(git_futils_mkdir_r(
|
|
"empty_standard_repo/test/ignore_me", NULL, 0775));
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!");
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/test/ignore_me/file2", "Me, too!");
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
|
cl_assert_equal_i(3, st.count);
|
|
|
|
cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file"));
|
|
cl_assert(st.status == GIT_STATUS_IGNORED);
|
|
|
|
cl_git_pass(
|
|
git_status_should_ignore(&ignored, g_repo, "test/ignore_me/file"));
|
|
cl_assert(ignored);
|
|
}
|
|
|
|
void test_status_ignore__subdirectories_recursion(void)
|
|
{
|
|
/* Let's try again with recursing into ignored dirs turned on */
|
|
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
|
status_entry_counts counts;
|
|
static const char *paths_r[] = {
|
|
".gitignore",
|
|
"ignore_also/file",
|
|
"ignore_me",
|
|
"test/ignore_me/and_me/file",
|
|
"test/ignore_me/file",
|
|
"test/ignore_me/file2",
|
|
};
|
|
static const unsigned int statuses_r[] = {
|
|
GIT_STATUS_WT_NEW,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
};
|
|
static const char *paths_nr[] = {
|
|
".gitignore",
|
|
"ignore_also/",
|
|
"ignore_me",
|
|
"test/ignore_me/",
|
|
};
|
|
static const unsigned int statuses_nr[] = {
|
|
GIT_STATUS_WT_NEW,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
GIT_STATUS_IGNORED,
|
|
};
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n");
|
|
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/ignore_me", "I'm going to be ignored!");
|
|
cl_git_pass(git_futils_mkdir_r(
|
|
"empty_standard_repo/test/ignore_me", NULL, 0775));
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!");
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/test/ignore_me/file2", "Me, too!");
|
|
cl_git_pass(git_futils_mkdir_r(
|
|
"empty_standard_repo/test/ignore_me/and_me", NULL, 0775));
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/test/ignore_me/and_me/file", "Deeply ignored");
|
|
cl_git_pass(git_futils_mkdir_r(
|
|
"empty_standard_repo/ignore_also", NULL, 0775));
|
|
cl_git_mkfile(
|
|
"empty_standard_repo/ignore_also/file", "I'm going to be ignored!");
|
|
|
|
memset(&counts, 0x0, sizeof(status_entry_counts));
|
|
counts.expected_entry_count = 6;
|
|
counts.expected_paths = paths_r;
|
|
counts.expected_statuses = statuses_r;
|
|
|
|
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);
|
|
|
|
|
|
memset(&counts, 0x0, sizeof(status_entry_counts));
|
|
counts.expected_entry_count = 4;
|
|
counts.expected_paths = paths_nr;
|
|
counts.expected_statuses = statuses_nr;
|
|
|
|
opts.flags = GIT_STATUS_OPT_DEFAULTS;
|
|
|
|
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;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n"));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n"));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n"));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_ignore_clear_internal_rules(g_repo));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_ignore_add_rule(
|
|
g_repo, "multiple\n*.rules\n# comment line\n*.bar\n"));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(ignored);
|
|
}
|
|
|
|
void test_status_ignore__add_internal_as_first_thing(void)
|
|
{
|
|
int ignored;
|
|
const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n";
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, add_me));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.tmp"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar"));
|
|
cl_assert(!ignored);
|
|
}
|
|
|
|
void test_status_ignore__internal_ignores_inside_deep_paths(void)
|
|
{
|
|
int ignored;
|
|
const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n";
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, add_me));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "Debug"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/Debug"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "really/Debug/this/file"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "Debug/what/I/say"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/NoDebug"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "NoDebug/this"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "please/NoDebug/this"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deep"));
|
|
cl_assert(ignored);
|
|
/* pattern containing slash gets FNM_PATHNAME so all slashes must match */
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/this/is/deep"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deep/too"));
|
|
cl_assert(ignored);
|
|
/* pattern containing slash gets FNM_PATHNAME so all slashes must match */
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "but/this/is/deep/and/ignored"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/not/deep"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "is/this/not/as/deep"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deepish"));
|
|
cl_assert(!ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "xthis/is/deep"));
|
|
cl_assert(!ignored);
|
|
}
|
|
|
|
void test_status_ignore__automatically_ignore_bad_files(void)
|
|
{
|
|
int ignored;
|
|
|
|
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, ".git"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/file/."));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/../funky"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/whatever.c"));
|
|
cl_assert(!ignored);
|
|
|
|
cl_git_pass(git_ignore_add_rule(g_repo, "*.c\n"));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, ".git"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/file/."));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/../funky"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/whatever.c"));
|
|
cl_assert(ignored);
|
|
|
|
cl_git_pass(git_ignore_clear_internal_rules(g_repo));
|
|
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, ".git"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/file/."));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/../funky"));
|
|
cl_assert(ignored);
|
|
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/whatever.c"));
|
|
cl_assert(!ignored);
|
|
}
|