| #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(); |
| } |
| |
| static void assert_ignored_( |
| bool expected, const char *filepath, const char *file, int line) |
| { |
| int is_ignored = 0; |
| cl_git_pass_( |
| git_status_should_ignore(&is_ignored, g_repo, filepath), file, line); |
| clar__assert( |
| (expected != 0) == (is_ignored != 0), |
| file, line, "expected != is_ignored", filepath, 1); |
| } |
| #define assert_ignored(expected, filepath) \ |
| assert_ignored_(expected, filepath, __FILE__, __LINE__) |
| #define assert_is_ignored(filepath) \ |
| assert_ignored_(true, filepath, __FILE__, __LINE__) |
| #define refute_is_ignored(filepath) \ |
| assert_ignored_(false, filepath, __FILE__, __LINE__) |
| |
| 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++) |
| assert_ignored(one_test->expected, one_test->path); |
| |
| /* confirm that ignore files were cached */ |
| cl_assert(git_attr_cache__is_cached( |
| g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/exclude")); |
| cl_assert(git_attr_cache__is_cached( |
| g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitignore")); |
| } |
| |
| |
| void test_status_ignore__1(void) |
| { |
| g_repo = cl_git_sandbox_init("attr"); |
| |
| cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n"); |
| git_attr_cache_flush(g_repo); |
| |
| assert_is_ignored("root_test4.txt"); |
| refute_is_ignored("sub/subdir_test2.txt"); |
| assert_is_ignored("dir"); |
| assert_is_ignored("dir/"); |
| refute_is_ignored("sub/dir"); |
| refute_is_ignored("sub/dir/"); |
| } |
| |
| void test_status_ignore__empty_repo_with_gitignore_rewrite(void) |
| { |
| status_entry_single st; |
| |
| 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); |
| |
| refute_is_ignored("look-ma.txt"); |
| |
| 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); |
| |
| refute_is_ignored("look-ma.txt"); |
| |
| 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); |
| |
| assert_is_ignored("look-ma.txt"); |
| } |
| |
| 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", 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 = (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0; |
| 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; |
| |
| 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); |
| |
| assert_is_ignored("ignore_me"); |
| |
| /* 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", 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); |
| |
| assert_is_ignored("test/ignore_me/file"); |
| } |
| |
| static void make_test_data(const char *reponame, const char **files) |
| { |
| const char **scan; |
| size_t repolen = strlen(reponame) + 1; |
| |
| g_repo = cl_git_sandbox_init(reponame); |
| |
| for (scan = files; *scan != NULL; ++scan) { |
| cl_git_pass(git_futils_mkdir_relative( |
| *scan + repolen, reponame, |
| 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST, NULL)); |
| 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 */ |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *paths_r[] = { |
| ".gitignore", |
| "dir/a/ignore_me", |
| "dir/b/ignore_me", |
| "dir/ignore_me", |
| "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, |
| GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, |
| }; |
| static const char *paths_nr[] = { |
| ".gitignore", |
| "dir/a/ignore_me", |
| "dir/b/ignore_me", |
| "dir/ignore_me", |
| "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, |
| GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, |
| }; |
| |
| 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)); |
| counts.expected_entry_count = 9; |
| 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 = 7; |
| 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__subdirectories_not_at_root(void) |
| { |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *paths_1[] = { |
| "dir/.gitignore", |
| "dir/a/ignore_me", |
| "dir/b/ignore_me", |
| "dir/ignore_me", |
| "ignore_also/file", |
| "ignore_me", |
| "test/.gitignore", |
| "test/ignore_me/and_me/file", |
| "test/ignore_me/file", |
| "test/ignore_me/file2", |
| }; |
| static const unsigned int statuses_1[] = { |
| GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, |
| GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, |
| GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, |
| }; |
| |
| 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"); |
| |
| memset(&counts, 0x0, sizeof(status_entry_counts)); |
| counts.expected_entry_count = 10; |
| counts.expected_paths = paths_1; |
| counts.expected_statuses = statuses_1; |
| |
| 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__leading_slash_ignores(void) |
| { |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *paths_2[] = { |
| "dir/.gitignore", |
| "dir/a/ignore_me", |
| "dir/b/ignore_me", |
| "dir/ignore_me", |
| "ignore_also/file", |
| "ignore_me", |
| "test/.gitignore", |
| "test/ignore_me/and_me/file", |
| "test/ignore_me/file", |
| "test/ignore_me/file2", |
| }; |
| static const unsigned int statuses_2[] = { |
| GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, |
| GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, |
| GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, |
| }; |
| |
| make_test_data(test_repo_1, test_files_1); |
| |
| cl_fake_home(); |
| cl_git_mkfile("home/.gitignore", "/ignore_me\n"); |
| { |
| git_config *cfg; |
| cl_git_pass(git_repository_config(&cfg, g_repo)); |
| cl_git_pass(git_config_set_string( |
| cfg, "core.excludesfile", "~/.gitignore")); |
| git_config_free(cfg); |
| } |
| |
| cl_git_rewritefile("empty_standard_repo/.git/info/exclude", "/ignore_also\n"); |
| cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "/ignore_me\n"); |
| cl_git_rewritefile("empty_standard_repo/test/.gitignore", "/and_me\n"); |
| |
| memset(&counts, 0x0, sizeof(status_entry_counts)); |
| counts.expected_entry_count = 10; |
| counts.expected_paths = paths_2; |
| counts.expected_statuses = statuses_2; |
| |
| 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__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 |
| }; |
| 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"); |
| |
| refute_is_ignored("subdir_match/aaa/subdir_match/file"); |
| assert_is_ignored("subdir_match/zzz_ignoreme"); |
| |
| 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__trailing_slash_star(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/file", |
| "empty_standard_repo/subdir/file", |
| "empty_standard_repo/subdir/sub2/sub3/file", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/subdir/.gitignore", "/**/*\n"); |
| |
| refute_is_ignored("file"); |
| assert_is_ignored("subdir/sub2/sub3/file"); |
| assert_is_ignored("subdir/file"); |
| } |
| |
| void test_status_ignore__adding_internal_ignores(void) |
| { |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| refute_is_ignored("one.txt"); |
| refute_is_ignored("two.bar"); |
| |
| cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n")); |
| |
| refute_is_ignored("one.txt"); |
| refute_is_ignored("two.bar"); |
| |
| cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n")); |
| |
| assert_is_ignored("one.txt"); |
| refute_is_ignored("two.bar"); |
| |
| cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n")); |
| |
| assert_is_ignored("one.txt"); |
| assert_is_ignored("two.bar"); |
| |
| cl_git_pass(git_ignore_clear_internal_rules(g_repo)); |
| |
| refute_is_ignored("one.txt"); |
| refute_is_ignored("two.bar"); |
| |
| cl_git_pass(git_ignore_add_rule( |
| g_repo, "multiple\n*.rules\n# comment line\n*.bar\n")); |
| |
| refute_is_ignored("one.txt"); |
| assert_is_ignored("two.bar"); |
| } |
| |
| void test_status_ignore__add_internal_as_first_thing(void) |
| { |
| 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)); |
| |
| assert_is_ignored("one.tmp"); |
| refute_is_ignored("two.bar"); |
| } |
| |
| void test_status_ignore__internal_ignores_inside_deep_paths(void) |
| { |
| 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)); |
| |
| assert_is_ignored("Debug"); |
| assert_is_ignored("and/Debug"); |
| assert_is_ignored("really/Debug/this/file"); |
| assert_is_ignored("Debug/what/I/say"); |
| |
| refute_is_ignored("and/NoDebug"); |
| refute_is_ignored("NoDebug/this"); |
| refute_is_ignored("please/NoDebug/this"); |
| |
| assert_is_ignored("this/is/deep"); |
| /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ |
| refute_is_ignored("and/this/is/deep"); |
| assert_is_ignored("this/is/deep/too"); |
| /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ |
| refute_is_ignored("but/this/is/deep/and/ignored"); |
| |
| refute_is_ignored("this/is/not/deep"); |
| refute_is_ignored("is/this/not/as/deep"); |
| refute_is_ignored("this/is/deepish"); |
| refute_is_ignored("xthis/is/deep"); |
| } |
| |
| void test_status_ignore__automatically_ignore_bad_files(void) |
| { |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| assert_is_ignored(".git"); |
| assert_is_ignored("this/file/."); |
| assert_is_ignored("path/../funky"); |
| refute_is_ignored("path/whatever.c"); |
| |
| cl_git_pass(git_ignore_add_rule(g_repo, "*.c\n")); |
| |
| assert_is_ignored(".git"); |
| assert_is_ignored("this/file/."); |
| assert_is_ignored("path/../funky"); |
| assert_is_ignored("path/whatever.c"); |
| |
| cl_git_pass(git_ignore_clear_internal_rules(g_repo)); |
| |
| assert_is_ignored(".git"); |
| assert_is_ignored("this/file/."); |
| assert_is_ignored("path/../funky"); |
| refute_is_ignored("path/whatever.c"); |
| } |
| |
| void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) |
| { |
| status_entry_single st; |
| char *test_cases[] = { |
| "!file", |
| "#blah", |
| "[blah]", |
| "[attr]", |
| "[attr]blah", |
| NULL |
| }; |
| int i; |
| |
| for (i = 0; *(test_cases + i) != NULL; i++) { |
| git_buf file = GIT_BUF_INIT; |
| char *file_name = *(test_cases + i); |
| git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| cl_git_pass(git_buf_joinpath(&file, "empty_standard_repo", file_name)); |
| cl_git_mkfile(git_buf_cstr(&file), "Please don't ignore me!"); |
| |
| memset(&st, 0, sizeof(st)); |
| cl_git_pass(git_status_foreach(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, repo, file_name)); |
| cl_assert(st.status == GIT_STATUS_WT_NEW); |
| |
| cl_git_sandbox_cleanup(); |
| git_buf_free(&file); |
| } |
| } |
| |
| void test_status_ignore__issue_1766_negated_ignores(void) |
| { |
| unsigned int status; |
| |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| cl_git_pass(git_futils_mkdir_r( |
| "empty_standard_repo/a", 0775)); |
| cl_git_mkfile( |
| "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); |
| cl_git_mkfile( |
| "empty_standard_repo/a/ignoreme", "I should be ignored\n"); |
| |
| refute_is_ignored("a/.gitignore"); |
| assert_is_ignored("a/ignoreme"); |
| |
| cl_git_pass(git_futils_mkdir_r( |
| "empty_standard_repo/b", 0775)); |
| cl_git_mkfile( |
| "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); |
| cl_git_mkfile( |
| "empty_standard_repo/b/ignoreme", "I should be ignored\n"); |
| |
| refute_is_ignored("b/.gitignore"); |
| assert_is_ignored("b/ignoreme"); |
| |
| /* shouldn't have changed results from first couple either */ |
| refute_is_ignored("a/.gitignore"); |
| assert_is_ignored("a/ignoreme"); |
| |
| /* status should find the two ignore files and nothing else */ |
| |
| cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore")); |
| cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); |
| |
| cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme")); |
| cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); |
| |
| cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore")); |
| cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); |
| |
| cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme")); |
| cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); |
| |
| { |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *paths[] = { |
| "a/.gitignore", |
| "a/ignoreme", |
| "b/.gitignore", |
| "b/ignoreme", |
| }; |
| static const unsigned int statuses[] = { |
| GIT_STATUS_WT_NEW, |
| GIT_STATUS_IGNORED, |
| GIT_STATUS_WT_NEW, |
| GIT_STATUS_IGNORED, |
| }; |
| |
| memset(&counts, 0x0, sizeof(status_entry_counts)); |
| counts.expected_entry_count = 4; |
| counts.expected_paths = paths; |
| counts.expected_statuses = statuses; |
| |
| 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); |
| } |
| } |
| |
| static void add_one_to_index(const char *file) |
| { |
| git_index *index; |
| cl_git_pass(git_repository_index(&index, g_repo)); |
| cl_git_pass(git_index_add_bypath(index, file)); |
| git_index_free(index); |
| } |
| |
| /* Some further broken scenarios that have been reported */ |
| void test_status_ignore__more_breakage(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", |
| "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked", |
| "empty_standard_repo/d1/pfx-d2/d3/d4/untracked", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "/d1/pfx-*\n" |
| "!/d1/pfx-d2/\n" |
| "/d1/pfx-d2/*\n" |
| "!/d1/pfx-d2/d3/\n" |
| "/d1/pfx-d2/d3/*\n" |
| "!/d1/pfx-d2/d3/d4/\n"); |
| add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked"); |
| |
| { |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *files[] = { |
| ".gitignore", |
| "d1/pfx-d2/d3/d4/d5/tracked", |
| "d1/pfx-d2/d3/d4/d5/untracked", |
| "d1/pfx-d2/d3/d4/untracked", |
| }; |
| static const unsigned int statuses[] = { |
| GIT_STATUS_WT_NEW, |
| GIT_STATUS_INDEX_NEW, |
| GIT_STATUS_WT_NEW, |
| GIT_STATUS_WT_NEW, |
| }; |
| |
| memset(&counts, 0x0, sizeof(status_entry_counts)); |
| counts.expected_entry_count = 4; |
| counts.expected_paths = files; |
| counts.expected_statuses = statuses; |
| opts.flags = GIT_STATUS_OPT_DEFAULTS | |
| GIT_STATUS_OPT_INCLUDE_IGNORED | |
| 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); |
| } |
| |
| refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked"); |
| refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked"); |
| refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); |
| } |
| |
| void test_status_ignore__negative_ignores_inside_ignores(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/top/mid/btm/tracked", |
| "empty_standard_repo/top/mid/btm/untracked", |
| "empty_standard_repo/zoo/bar", |
| "empty_standard_repo/zoo/foo/bar", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "top\n" |
| "!top/mid/btm\n" |
| "zoo/*\n" |
| "!zoo/bar\n" |
| "!zoo/foo/bar\n"); |
| add_one_to_index("top/mid/btm/tracked"); |
| |
| { |
| git_status_options opts = GIT_STATUS_OPTIONS_INIT; |
| status_entry_counts counts; |
| static const char *files[] = { |
| ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked", |
| "zoo/bar", "zoo/foo/bar", |
| }; |
| static const unsigned int statuses[] = { |
| GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_IGNORED, |
| GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, |
| }; |
| |
| memset(&counts, 0x0, sizeof(status_entry_counts)); |
| counts.expected_entry_count = 5; |
| counts.expected_paths = files; |
| counts.expected_statuses = statuses; |
| opts.flags = GIT_STATUS_OPT_DEFAULTS | |
| GIT_STATUS_OPT_INCLUDE_IGNORED | |
| 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); |
| } |
| |
| assert_is_ignored("top/mid/btm/tracked"); |
| assert_is_ignored("top/mid/btm/untracked"); |
| refute_is_ignored("foo/bar"); |
| } |
| |
| void test_status_ignore__negative_ignores_in_slash_star(void) |
| { |
| git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; |
| git_status_list *list; |
| int found_look_ma = 0, found_what_about = 0; |
| size_t i; |
| static const char *test_files[] = { |
| "empty_standard_repo/bin/look-ma.txt", |
| "empty_standard_repo/bin/what-about-me.txt", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "bin/*\n" |
| "!bin/w*\n"); |
| |
| assert_is_ignored("bin/look-ma.txt"); |
| refute_is_ignored("bin/what-about-me.txt"); |
| |
| status_opts.flags = GIT_STATUS_OPT_DEFAULTS; |
| cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); |
| for (i = 0; i < git_status_list_entrycount(list); i++) { |
| const git_status_entry *entry = git_status_byindex(list, i); |
| |
| if (!strcmp("bin/look-ma.txt", entry->index_to_workdir->new_file.path)) |
| found_look_ma = 1; |
| |
| if (!strcmp("bin/what-about-me.txt", entry->index_to_workdir->new_file.path)) |
| found_what_about = 1; |
| } |
| git_status_list_free(list); |
| |
| cl_assert(found_look_ma); |
| cl_assert(found_what_about); |
| } |
| |
| void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores(void) |
| { |
| git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; |
| git_status_list *list; |
| int found_parent_file = 0, found_parent_child1_file = 0, found_parent_child2_file = 0; |
| size_t i; |
| static const char *test_files[] = { |
| "empty_standard_repo/parent/file.txt", |
| "empty_standard_repo/parent/force.txt", |
| "empty_standard_repo/parent/child1/file.txt", |
| "empty_standard_repo/parent/child2/file.txt", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "parent/*\n" |
| "!parent/force.txt\n" |
| "!parent/child1\n" |
| "!parent/child2/\n"); |
| |
| add_one_to_index("parent/force.txt"); |
| |
| assert_is_ignored("parent/file.txt"); |
| refute_is_ignored("parent/force.txt"); |
| refute_is_ignored("parent/child1/file.txt"); |
| refute_is_ignored("parent/child2/file.txt"); |
| |
| status_opts.flags = GIT_STATUS_OPT_DEFAULTS; |
| cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); |
| for (i = 0; i < git_status_list_entrycount(list); i++) { |
| const git_status_entry *entry = git_status_byindex(list, i); |
| |
| if (!entry->index_to_workdir) |
| continue; |
| |
| if (!strcmp("parent/file.txt", entry->index_to_workdir->new_file.path)) |
| found_parent_file = 1; |
| |
| if (!strcmp("parent/force.txt", entry->index_to_workdir->new_file.path)) |
| found_parent_file = 1; |
| |
| if (!strcmp("parent/child1/file.txt", entry->index_to_workdir->new_file.path)) |
| found_parent_child1_file = 1; |
| |
| if (!strcmp("parent/child2/file.txt", entry->index_to_workdir->new_file.path)) |
| found_parent_child2_file = 1; |
| } |
| git_status_list_free(list); |
| |
| cl_assert(found_parent_file); |
| cl_assert(found_parent_child1_file); |
| cl_assert(found_parent_child2_file); |
| } |
| |
| void test_status_ignore__negative_directory_ignores(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/parent/child1/bar.txt", |
| "empty_standard_repo/parent/child2/bar.txt", |
| "empty_standard_repo/parent/child3/foo.txt", |
| "empty_standard_repo/parent/child4/bar.txt", |
| "empty_standard_repo/parent/nested/child5/bar.txt", |
| "empty_standard_repo/parent/nested/child6/bar.txt", |
| "empty_standard_repo/parent/nested/child7/bar.txt", |
| "empty_standard_repo/padded_parent/child8/bar.txt", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "foo.txt\n" |
| "parent/child1\n" |
| "parent/child2\n" |
| "parent/child4\n" |
| "parent/nested/child5\n" |
| "nested/child6\n" |
| "nested/child7\n" |
| "padded_parent/child8\n" |
| /* test simple exact match */ |
| "!parent/child1\n" |
| /* test negating file without negating dir */ |
| "!parent/child2/bar.txt\n" |
| /* test negative pattern on dir with its content |
| * being ignored */ |
| "!parent/child3\n" |
| /* test with partial match at end */ |
| "!child4\n" |
| /* test with partial match with '/' at end */ |
| "!nested/child5\n" |
| /* test with complete match */ |
| "!nested/child6\n" |
| /* test with trailing '/' */ |
| "!child7/\n" |
| /* test with partial dir match */ |
| "!_parent/child8\n"); |
| |
| refute_is_ignored("parent/child1/bar.txt"); |
| assert_is_ignored("parent/child2/bar.txt"); |
| assert_is_ignored("parent/child3/foo.txt"); |
| refute_is_ignored("parent/child4/bar.txt"); |
| assert_is_ignored("parent/nested/child5/bar.txt"); |
| refute_is_ignored("parent/nested/child6/bar.txt"); |
| refute_is_ignored("parent/nested/child7/bar.txt"); |
| assert_is_ignored("padded_parent/child8/bar.txt"); |
| } |
| |
| void test_status_ignore__unignore_entry_in_ignored_dir(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/bar.txt", |
| "empty_standard_repo/parent/bar.txt", |
| "empty_standard_repo/parent/child/bar.txt", |
| "empty_standard_repo/nested/parent/child/bar.txt", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "bar.txt\n" |
| "!parent/child/bar.txt\n"); |
| |
| assert_is_ignored("bar.txt"); |
| assert_is_ignored("parent/bar.txt"); |
| refute_is_ignored("parent/child/bar.txt"); |
| assert_is_ignored("nested/parent/child/bar.txt"); |
| } |
| |
| void test_status_ignore__do_not_unignore_basename_prefix(void) |
| { |
| static const char *test_files[] = { |
| "empty_standard_repo/foo_bar.txt", |
| NULL |
| }; |
| |
| make_test_data("empty_standard_repo", test_files); |
| cl_git_mkfile( |
| "empty_standard_repo/.gitignore", |
| "foo_bar.txt\n" |
| "!bar.txt\n"); |
| |
| assert_is_ignored("foo_bar.txt"); |
| } |
| |
| void test_status_ignore__filename_with_cr(void) |
| { |
| int ignored; |
| |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\r\n"); |
| |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); |
| cl_assert_equal_i(1, ignored); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\n"); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); |
| cl_assert_equal_i(1, ignored); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\n"); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); |
| cl_assert_equal_i(1, ignored); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); |
| cl_assert_equal_i(0, ignored); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\r\n"); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); |
| cl_assert_equal_i(1, ignored); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); |
| cl_assert_equal_i(0, ignored); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\n"); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); |
| cl_assert_equal_i(0, ignored); |
| 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"); |
| git_config_free(cfg); |
| 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); |
| } |
| |
| void test_status_ignore__negate_exact_previous(void) |
| { |
| int ignored; |
| |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", "*.com\ntags\n!tags/\n.buildpath"); |
| cl_git_mkfile("empty_standard_repo/.buildpath", ""); |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, ".buildpath")); |
| cl_assert_equal_i(1, ignored); |
| } |
| |
| void test_status_ignore__negate_starstar(void) |
| { |
| int ignored; |
| |
| g_repo = cl_git_sandbox_init("empty_standard_repo"); |
| |
| cl_git_mkfile("empty_standard_repo/.gitignore", |
| "code/projects/**/packages/*\n" |
| "!code/projects/**/packages/repositories.config"); |
| |
| cl_git_pass(git_futils_mkdir_r("empty_standard_repo/code/projects/foo/bar/packages", 0777)); |
| cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); |
| |
| cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); |
| cl_assert_equal_i(0, ignored); |
| } |