mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-30 06:13:36 +00:00

To answer if a single given file should be ignored, the path to that file has to be processed progressively checking that there are no intermediate ignored directories in getting to the file in question. This enables that, fixing the broken old behavior, and adds tests to exercise various ignore situations.
342 lines
10 KiB
C
342 lines
10 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;
|
|
git_status_options opts;
|
|
|
|
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);
|
|
|
|
|
|
/* So, interestingly, as per the comment in diff_from_iterators() the
|
|
* following file is ignored, but in a way so that it does not show up
|
|
* in status even if INCLUDE_IGNORED is used. This actually matches
|
|
* core git's behavior - if you follow these steps and try running "git
|
|
* status -uall --ignored" then the following file and directory will
|
|
* not show up in the output at all.
|
|
*/
|
|
|
|
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!");
|
|
|
|
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
|
|
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
|
|
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
|
|
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
|
|
|
|
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_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__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);
|
|
}
|