From 65ad6b5afdc5df17a706ef36767bffcbc8c9ab55 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 08:21:59 +0200 Subject: [PATCH 1/7] tests: remove accidentally checked in backup file The "ignore.c.bak" file has accidentally been checked in via commit 191649010 (ignore: test that leading whitespace is significant, 2019-05-19) and should obviously not be part of our test suites. Delete it. --- tests/status/ignore.c.bak | 1268 ------------------------------------- 1 file changed, 1268 deletions(-) delete mode 100644 tests/status/ignore.c.bak diff --git a/tests/status/ignore.c.bak b/tests/status/ignore.c.bak deleted file mode 100644 index c43f31458..000000000 --- a/tests/status/ignore.c.bak +++ /dev/null @@ -1,1268 +0,0 @@ -#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_expect( - git_status_should_ignore(&is_ignored, g_repo, filepath), 0, 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_INDEX_CAPABILITY_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_dispose(&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); -} - -void test_status_ignore__ignore_all_toplevel_dirs_include_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/README.md", - "empty_standard_repo/src/main.c", - "empty_standard_repo/src/foo/foo.c", - "empty_standard_repo/dist/foo.o", - "empty_standard_repo/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "/*/\n" - "!/src\n"); - - assert_is_ignored("dist/foo.o"); - assert_is_ignored("dist/main.o"); - - refute_is_ignored("README.md"); - refute_is_ignored("src/foo.c"); - refute_is_ignored("src/foo/foo.c"); -} - -void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/project/README.md", - "empty_standard_repo/project/src/main.c", - "empty_standard_repo/project/src/foo/foo.c", - "empty_standard_repo/project/dist/foo.o", - "empty_standard_repo/project/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/project/.gitignore", - "/*/\n" - "!/src\n"); - - assert_is_ignored("project/dist/foo.o"); - assert_is_ignored("project/dist/main.o"); - - refute_is_ignored("project/src/foo.c"); - refute_is_ignored("project/src/foo/foo.c"); - refute_is_ignored("project/README.md"); -} - -void test_status_ignore__subdir_ignore_everything_except_certain_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/project/README.md", - "empty_standard_repo/project/some_file", - "empty_standard_repo/project/src/main.c", - "empty_standard_repo/project/src/foo/foo.c", - "empty_standard_repo/project/dist/foo.o", - "empty_standard_repo/project/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/project/.gitignore", - "/*\n" - "!/src\n" - "!README.md\n"); - - assert_is_ignored("project/some_file"); - assert_is_ignored("project/dist/foo.o"); - assert_is_ignored("project/dist/main.o"); - - refute_is_ignored("project/README.md"); - refute_is_ignored("project/src/foo.c"); - refute_is_ignored("project/src/foo/foo.c"); -} - -void test_status_ignore__deeper(void) -{ - const char *test_files[] = { - "empty_standard_repo/foo.data", - "empty_standard_repo/bar.data", - "empty_standard_repo/dont_ignore/foo.data", - "empty_standard_repo/dont_ignore/bar.data", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile("empty_standard_repo/.gitignore", - "*.data\n" - "!dont_ignore/*.data\n"); - - assert_is_ignored("foo.data"); - assert_is_ignored("bar.data"); - - refute_is_ignored("dont_ignore/foo.data"); - refute_is_ignored("dont_ignore/bar.data"); -} - -void test_status_ignore__unignored_dir_with_ignored_contents(void) -{ - static const char *test_files[] = { - "empty_standard_repo/dir/a.test", - "empty_standard_repo/dir/subdir/a.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "*.test\n" - "!dir/*\n"); - - refute_is_ignored("dir/a.test"); - assert_is_ignored("dir/subdir/a.test"); -} - -void test_status_ignore__unignored_subdirs(void) -{ - static const char *test_files[] = { - "empty_standard_repo/dir/a.test", - "empty_standard_repo/dir/subdir/a.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "dir/*\n" - "!dir/*/\n"); - - assert_is_ignored("dir/a.test"); - refute_is_ignored("dir/subdir/a.test"); -} - -void test_status_ignore__skips_bom(void) -{ - static const char *test_files[] = { - "empty_standard_repo/a.test", - "empty_standard_repo/b.test", - "empty_standard_repo/c.test", - "empty_standard_repo/foo.txt", - "empty_standard_repo/bar.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "\xEF\xBB\xBF*.test\n"); - - assert_is_ignored("a.test"); - assert_is_ignored("b.test"); - assert_is_ignored("c.test"); - refute_is_ignored("foo.txt"); - refute_is_ignored("bar.txt"); -} - -void test_status_ignore__leading_spaces_are_significant(void) -{ - static const char *test_files[] = { - "empty_standard_repo/a.test", - "empty_standard_repo/b.test", - "empty_standard_repo/c.test", - "empty_standard_repo/d.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - " a.test\n" - "# this is a comment\n" - "b.test\n" - "\tc.test\n" - " # not a comment\n" - "d.test\n"); - - refute_is_ignored("a.test"); - assert_is_ignored(" a.test"); - refute_is_ignored("# this is a comment"); - assert_is_ignored("b.test"); - refute_is_ignored("c.test"); - assert_is_ignored("\tc.test"); - assert_is_ignored(" # not a comment"); - assert_is_ignored("d.test"); -} From 01dda5fffcc8527d20eb65045f68c8f30697cba9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 08:29:32 +0200 Subject: [PATCH 2/7] tests: unify ignore tests into their own dir We had several occasions where tests for the gitignore had been added to status::ignore instead of the easier-to-handle attr::ignore test suite. This most likely resulted from the fact that the attr::ignore test suite is not easy to discover inside of the attr folder. Furthermore, ignore being part of the attributes code is an implementation detail, only, and thus shouldn't be stressed as much. Improve this by moving both attr::ignore and status::ignore tests into a new ignore test suite. --- tests/{attr/ignore.c => ignore/path.c} | 56 +++++++-------- tests/{status/ignore.c => ignore/status.c} | 80 +++++++++++----------- 2 files changed, 68 insertions(+), 68 deletions(-) rename tests/{attr/ignore.c => ignore/path.c} (89%) rename tests/{status/ignore.c => ignore/status.c} (94%) diff --git a/tests/attr/ignore.c b/tests/ignore/path.c similarity index 89% rename from tests/attr/ignore.c rename to tests/ignore/path.c index ea8a14192..425a49ca9 100644 --- a/tests/attr/ignore.c +++ b/tests/ignore/path.c @@ -5,12 +5,12 @@ static git_repository *g_repo = NULL; -void test_attr_ignore__initialize(void) +void test_ignore_path__initialize(void) { g_repo = cl_git_sandbox_init("attr"); } -void test_attr_ignore__cleanup(void) +void test_ignore_path__cleanup(void) { cl_git_sandbox_cleanup(); g_repo = NULL; @@ -31,7 +31,7 @@ static void assert_is_ignored_( #define assert_is_ignored(expected, filepath) \ assert_is_ignored_(expected, filepath, __FILE__, __LINE__) -void test_attr_ignore__honor_temporary_rules(void) +void test_ignore_path__honor_temporary_rules(void) { cl_git_rewritefile("attr/.gitignore", "/NewFolder\n/NewFolder/NewFolder"); @@ -41,7 +41,7 @@ void test_attr_ignore__honor_temporary_rules(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__allow_root(void) +void test_ignore_path__allow_root(void) { cl_git_rewritefile("attr/.gitignore", "/"); @@ -51,7 +51,7 @@ void test_attr_ignore__allow_root(void) assert_is_ignored(false, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__ignore_space(void) +void test_ignore_path__ignore_space(void) { cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder \n/NewFolder/NewFolder"); @@ -61,7 +61,7 @@ void test_attr_ignore__ignore_space(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__intermittent_space(void) +void test_ignore_path__intermittent_space(void) { cl_git_rewritefile("attr/.gitignore", "foo bar\n"); @@ -70,7 +70,7 @@ void test_attr_ignore__intermittent_space(void) assert_is_ignored(true, "foo bar"); } -void test_attr_ignore__trailing_space(void) +void test_ignore_path__trailing_space(void) { cl_git_rewritefile( "attr/.gitignore", @@ -85,7 +85,7 @@ void test_attr_ignore__trailing_space(void) assert_is_ignored(false, "bar "); } -void test_attr_ignore__escaped_trailing_spaces(void) +void test_ignore_path__escaped_trailing_spaces(void) { cl_git_rewritefile( "attr/.gitignore", @@ -107,7 +107,7 @@ void test_attr_ignore__escaped_trailing_spaces(void) assert_is_ignored(false, "qux "); } -void test_attr_ignore__ignore_dir(void) +void test_ignore_path__ignore_dir(void) { cl_git_rewritefile("attr/.gitignore", "dir/\n"); @@ -115,7 +115,7 @@ void test_attr_ignore__ignore_dir(void) assert_is_ignored(true, "dir/file"); } -void test_attr_ignore__ignore_dir_with_trailing_space(void) +void test_ignore_path__ignore_dir_with_trailing_space(void) { cl_git_rewritefile("attr/.gitignore", "dir/ \n"); @@ -123,7 +123,7 @@ void test_attr_ignore__ignore_dir_with_trailing_space(void) assert_is_ignored(true, "dir/file"); } -void test_attr_ignore__ignore_root(void) +void test_ignore_path__ignore_root(void) { cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder"); @@ -133,7 +133,7 @@ void test_attr_ignore__ignore_root(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__full_paths(void) +void test_ignore_path__full_paths(void) { cl_git_rewritefile("attr/.gitignore", "Folder/*/Contained"); @@ -153,7 +153,7 @@ void test_attr_ignore__full_paths(void) assert_is_ignored(false, "Folder/Middle/More/More/Contained/Not/Happy/Child"); } -void test_attr_ignore__more_starstar_cases(void) +void test_ignore_path__more_starstar_cases(void) { cl_must_pass(p_unlink("attr/.gitignore")); cl_git_mkfile( @@ -171,7 +171,7 @@ void test_attr_ignore__more_starstar_cases(void) assert_is_ignored(false, "sub/sub2/aaa.html"); } -void test_attr_ignore__leading_stars(void) +void test_ignore_path__leading_stars(void) { cl_git_rewritefile( "attr/.gitignore", @@ -204,7 +204,7 @@ void test_attr_ignore__leading_stars(void) assert_is_ignored(false, "dir1/kid2/file"); } -void test_attr_ignore__globs_and_path_delimiters(void) +void test_ignore_path__globs_and_path_delimiters(void) { cl_git_rewritefile("attr/.gitignore", "foo/bar/**"); assert_is_ignored(true, "foo/bar/baz"); @@ -230,7 +230,7 @@ void test_attr_ignore__globs_and_path_delimiters(void) assert_is_ignored(false, "_test/foo/bar/code/file"); } -void test_attr_ignore__skip_gitignore_directory(void) +void test_ignore_path__skip_gitignore_directory(void) { cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder"); p_unlink("attr/.gitignore"); @@ -244,7 +244,7 @@ void test_attr_ignore__skip_gitignore_directory(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__subdirectory_gitignore(void) +void test_ignore_path__subdirectory_gitignore(void) { p_unlink("attr/.gitignore"); cl_assert(!git_path_exists("attr/.gitignore")); @@ -262,7 +262,7 @@ void test_attr_ignore__subdirectory_gitignore(void) assert_is_ignored(false, "dir/file3"); } -void test_attr_ignore__expand_tilde_to_homedir(void) +void test_ignore_path__expand_tilde_to_homedir(void) { git_config *cfg; @@ -292,7 +292,7 @@ void test_attr_ignore__expand_tilde_to_homedir(void) /* Ensure that the .gitignore in the subdirectory only affects * items in the subdirectory. */ -void test_attr_ignore__gitignore_in_subdir(void) +void test_ignore_path__gitignore_in_subdir(void) { cl_git_rmfile("attr/.gitignore"); @@ -320,7 +320,7 @@ void test_attr_ignore__gitignore_in_subdir(void) } /* Ensure that files do not match folder cases */ -void test_attr_ignore__dont_ignore_files_for_folder(void) +void test_ignore_path__dont_ignore_files_for_folder(void) { cl_git_rmfile("attr/.gitignore"); @@ -351,7 +351,7 @@ void test_attr_ignore__dont_ignore_files_for_folder(void) assert_is_ignored(false, "dir/TeSt"); } -void test_attr_ignore__symlink_to_outside(void) +void test_ignore_path__symlink_to_outside(void) { #ifdef GIT_WIN32 cl_skip(); @@ -364,7 +364,7 @@ void test_attr_ignore__symlink_to_outside(void) assert_is_ignored(true, "lala/../symlink"); } -void test_attr_ignore__test(void) +void test_ignore_path__test(void) { cl_git_rewritefile("attr/.gitignore", "/*/\n" @@ -376,7 +376,7 @@ void test_attr_ignore__test(void) assert_is_ignored(true, "bin/foo"); } -void test_attr_ignore__unignore_dir_succeeds(void) +void test_ignore_path__unignore_dir_succeeds(void) { cl_git_rewritefile("attr/.gitignore", "*.c\n" @@ -385,7 +385,7 @@ void test_attr_ignore__unignore_dir_succeeds(void) assert_is_ignored(true, "src/foo/foo.c"); } -void test_attr_ignore__case_insensitive_unignores_previous_rule(void) +void test_ignore_path__case_insensitive_unignores_previous_rule(void) { git_config *cfg; @@ -402,7 +402,7 @@ void test_attr_ignore__case_insensitive_unignores_previous_rule(void) assert_is_ignored(false, "case/file"); } -void test_attr_ignore__case_sensitive_unignore_does_nothing(void) +void test_ignore_path__case_sensitive_unignore_does_nothing(void) { git_config *cfg; @@ -419,7 +419,7 @@ void test_attr_ignore__case_sensitive_unignore_does_nothing(void) assert_is_ignored(true, "case/file"); } -void test_attr_ignore__ignored_subdirfiles_with_subdir_rule(void) +void test_ignore_path__ignored_subdirfiles_with_subdir_rule(void) { cl_git_rewritefile( "attr/.gitignore", @@ -431,7 +431,7 @@ void test_attr_ignore__ignored_subdirfiles_with_subdir_rule(void) assert_is_ignored(true, "dir/sub1/sub2"); } -void test_attr_ignore__ignored_subdirfiles_with_negations(void) +void test_ignore_path__ignored_subdirfiles_with_negations(void) { cl_git_rewritefile( "attr/.gitignore", @@ -443,7 +443,7 @@ void test_attr_ignore__ignored_subdirfiles_with_negations(void) assert_is_ignored(true, "dir/sub1/c.test"); } -void test_attr_ignore__negative_directory_rules_only_match_directories(void) +void test_ignore_path__negative_directory_rules_only_match_directories(void) { cl_git_rewritefile( "attr/.gitignore", diff --git a/tests/status/ignore.c b/tests/ignore/status.c similarity index 94% rename from tests/status/ignore.c rename to tests/ignore/status.c index a93ff8b56..2c32f4486 100644 --- a/tests/status/ignore.c +++ b/tests/ignore/status.c @@ -3,15 +3,15 @@ #include "git2/attr.h" #include "ignore.h" #include "attr.h" -#include "status_helpers.h" +#include "status/status_helpers.h" static git_repository *g_repo = NULL; -void test_status_ignore__initialize(void) +void test_ignore_status__initialize(void) { } -void test_status_ignore__cleanup(void) +void test_ignore_status__cleanup(void) { cl_git_sandbox_cleanup(); } @@ -33,7 +33,7 @@ static void assert_ignored_( #define refute_is_ignored(filepath) \ assert_ignored_(false, filepath, __FILE__, __LINE__) -void test_status_ignore__0(void) +void test_ignore_status__0(void) { struct { const char *path; @@ -75,7 +75,7 @@ void test_status_ignore__0(void) } -void test_status_ignore__1(void) +void test_ignore_status__1(void) { g_repo = cl_git_sandbox_init("attr"); @@ -90,7 +90,7 @@ void test_status_ignore__1(void) refute_is_ignored("sub/dir/"); } -void test_status_ignore__empty_repo_with_gitignore_rewrite(void) +void test_ignore_status__empty_repo_with_gitignore_rewrite(void) { status_entry_single st; @@ -134,7 +134,7 @@ void test_status_ignore__empty_repo_with_gitignore_rewrite(void) assert_is_ignored("look-ma.txt"); } -void test_status_ignore__ignore_pattern_contains_space(void) +void test_ignore_status__ignore_pattern_contains_space(void) { unsigned int flags; const mode_t mode = 0777; @@ -155,7 +155,7 @@ void test_status_ignore__ignore_pattern_contains_space(void) cl_assert(flags == GIT_STATUS_WT_NEW); } -void test_status_ignore__ignore_pattern_ignorecase(void) +void test_ignore_status__ignore_pattern_ignorecase(void) { unsigned int flags; bool ignore_case; @@ -174,7 +174,7 @@ void test_status_ignore__ignore_pattern_ignorecase(void) cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW); } -void test_status_ignore__subdirectories(void) +void test_ignore_status__subdirectories(void) { status_entry_single st; @@ -250,7 +250,7 @@ static const char *test_files_1[] = { NULL }; -void test_status_ignore__subdirectories_recursion(void) +void test_ignore_status__subdirectories_recursion(void) { /* Let's try again with recursing into ignored dirs turned on */ git_status_options opts = GIT_STATUS_OPTIONS_INIT; @@ -319,7 +319,7 @@ void test_status_ignore__subdirectories_recursion(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__subdirectories_not_at_root(void) +void test_ignore_status__subdirectories_not_at_root(void) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; status_entry_counts counts; @@ -360,7 +360,7 @@ void test_status_ignore__subdirectories_not_at_root(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__leading_slash_ignores(void) +void test_ignore_status__leading_slash_ignores(void) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; status_entry_counts counts; @@ -413,7 +413,7 @@ void test_status_ignore__leading_slash_ignores(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__multiple_leading_slash(void) +void test_ignore_status__multiple_leading_slash(void) { static const char *test_files[] = { "empty_standard_repo/a.test", @@ -437,7 +437,7 @@ void test_status_ignore__multiple_leading_slash(void) refute_is_ignored("d.test"); } -void test_status_ignore__contained_dir_with_matching_name(void) +void test_ignore_status__contained_dir_with_matching_name(void) { static const char *test_files[] = { "empty_standard_repo/subdir_match/aaa/subdir_match/file", @@ -477,7 +477,7 @@ void test_status_ignore__contained_dir_with_matching_name(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__trailing_slash_star(void) +void test_ignore_status__trailing_slash_star(void) { static const char *test_files[] = { "empty_standard_repo/file", @@ -495,7 +495,7 @@ void test_status_ignore__trailing_slash_star(void) assert_is_ignored("subdir/file"); } -void test_status_ignore__adding_internal_ignores(void) +void test_ignore_status__adding_internal_ignores(void) { g_repo = cl_git_sandbox_init("empty_standard_repo"); @@ -529,7 +529,7 @@ void test_status_ignore__adding_internal_ignores(void) assert_is_ignored("two.bar"); } -void test_status_ignore__add_internal_as_first_thing(void) +void test_ignore_status__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"; @@ -541,7 +541,7 @@ void test_status_ignore__add_internal_as_first_thing(void) refute_is_ignored("two.bar"); } -void test_status_ignore__internal_ignores_inside_deep_paths(void) +void test_ignore_status__internal_ignores_inside_deep_paths(void) { const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n"; @@ -571,7 +571,7 @@ void test_status_ignore__internal_ignores_inside_deep_paths(void) refute_is_ignored("xthis/is/deep"); } -void test_status_ignore__automatically_ignore_bad_files(void) +void test_ignore_status__automatically_ignore_bad_files(void) { g_repo = cl_git_sandbox_init("empty_standard_repo"); @@ -595,7 +595,7 @@ void test_status_ignore__automatically_ignore_bad_files(void) refute_is_ignored("path/whatever.c"); } -void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) +void test_ignore_status__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) { status_entry_single st; char *test_cases[] = { @@ -629,7 +629,7 @@ void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_s } } -void test_status_ignore__issue_1766_negated_ignores(void) +void test_ignore_status__issue_1766_negated_ignores(void) { unsigned int status; @@ -714,7 +714,7 @@ static void add_one_to_index(const char *file) } /* Some further broken scenarios that have been reported */ -void test_status_ignore__more_breakage(void) +void test_ignore_status__more_breakage(void) { static const char *test_files[] = { "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", @@ -770,7 +770,7 @@ void test_status_ignore__more_breakage(void) refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); } -void test_status_ignore__negative_ignores_inside_ignores(void) +void test_ignore_status__negative_ignores_inside_ignores(void) { static const char *test_files[] = { "empty_standard_repo/top/mid/btm/tracked", @@ -822,7 +822,7 @@ void test_status_ignore__negative_ignores_inside_ignores(void) refute_is_ignored("foo/bar"); } -void test_status_ignore__negative_ignores_in_slash_star(void) +void test_ignore_status__negative_ignores_in_slash_star(void) { git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list; @@ -860,7 +860,7 @@ void test_status_ignore__negative_ignores_in_slash_star(void) cl_assert(found_what_about); } -void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores(void) +void test_ignore_status__negative_ignores_without_trailing_slash_inside_ignores(void) { git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list; @@ -916,7 +916,7 @@ void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores( cl_assert(found_parent_child2_file); } -void test_status_ignore__negative_directory_ignores(void) +void test_ignore_status__negative_directory_ignores(void) { static const char *test_files[] = { "empty_standard_repo/parent/child1/bar.txt", @@ -969,7 +969,7 @@ void test_status_ignore__negative_directory_ignores(void) assert_is_ignored("padded_parent/child8/bar.txt"); } -void test_status_ignore__unignore_entry_in_ignored_dir(void) +void test_ignore_status__unignore_entry_in_ignored_dir(void) { static const char *test_files[] = { "empty_standard_repo/bar.txt", @@ -991,7 +991,7 @@ void test_status_ignore__unignore_entry_in_ignored_dir(void) assert_is_ignored("nested/parent/child/bar.txt"); } -void test_status_ignore__do_not_unignore_basename_prefix(void) +void test_ignore_status__do_not_unignore_basename_prefix(void) { static const char *test_files[] = { "empty_standard_repo/foo_bar.txt", @@ -1007,7 +1007,7 @@ void test_status_ignore__do_not_unignore_basename_prefix(void) assert_is_ignored("foo_bar.txt"); } -void test_status_ignore__filename_with_cr(void) +void test_ignore_status__filename_with_cr(void) { int ignored; @@ -1040,7 +1040,7 @@ void test_status_ignore__filename_with_cr(void) cl_assert_equal_i(1, ignored); } -void test_status_ignore__subdir_doesnt_match_above(void) +void test_ignore_status__subdir_doesnt_match_above(void) { int ignored, icase = 0, error; git_config *cfg; @@ -1073,7 +1073,7 @@ void test_status_ignore__subdir_doesnt_match_above(void) cl_assert_equal_i(icase, ignored); } -void test_status_ignore__negate_exact_previous(void) +void test_ignore_status__negate_exact_previous(void) { int ignored; @@ -1085,7 +1085,7 @@ void test_status_ignore__negate_exact_previous(void) cl_assert_equal_i(1, ignored); } -void test_status_ignore__negate_starstar(void) +void test_ignore_status__negate_starstar(void) { int ignored; @@ -1102,7 +1102,7 @@ void test_status_ignore__negate_starstar(void) cl_assert_equal_i(0, ignored); } -void test_status_ignore__ignore_all_toplevel_dirs_include_files(void) +void test_ignore_status__ignore_all_toplevel_dirs_include_files(void) { static const char *test_files[] = { "empty_standard_repo/README.md", @@ -1127,7 +1127,7 @@ void test_status_ignore__ignore_all_toplevel_dirs_include_files(void) refute_is_ignored("src/foo/foo.c"); } -void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void) +void test_ignore_status__subdir_ignore_all_toplevel_dirs_include_files(void) { static const char *test_files[] = { "empty_standard_repo/project/README.md", @@ -1152,7 +1152,7 @@ void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void) refute_is_ignored("project/README.md"); } -void test_status_ignore__subdir_ignore_everything_except_certain_files(void) +void test_ignore_status__subdir_ignore_everything_except_certain_files(void) { static const char *test_files[] = { "empty_standard_repo/project/README.md", @@ -1180,7 +1180,7 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void) refute_is_ignored("project/src/foo/foo.c"); } -void test_status_ignore__deeper(void) +void test_ignore_status__deeper(void) { const char *test_files[] = { "empty_standard_repo/foo.data", @@ -1202,7 +1202,7 @@ void test_status_ignore__deeper(void) refute_is_ignored("dont_ignore/bar.data"); } -void test_status_ignore__unignored_dir_with_ignored_contents(void) +void test_ignore_status__unignored_dir_with_ignored_contents(void) { static const char *test_files[] = { "empty_standard_repo/dir/a.test", @@ -1220,7 +1220,7 @@ void test_status_ignore__unignored_dir_with_ignored_contents(void) assert_is_ignored("dir/subdir/a.test"); } -void test_status_ignore__unignored_subdirs(void) +void test_ignore_status__unignored_subdirs(void) { static const char *test_files[] = { "empty_standard_repo/dir/a.test", @@ -1238,7 +1238,7 @@ void test_status_ignore__unignored_subdirs(void) refute_is_ignored("dir/subdir/a.test"); } -void test_status_ignore__skips_bom(void) +void test_ignore_status__skips_bom(void) { static const char *test_files[] = { "empty_standard_repo/a.test", @@ -1261,7 +1261,7 @@ void test_status_ignore__skips_bom(void) refute_is_ignored("bar.txt"); } -void test_status_ignore__leading_spaces_are_significant(void) +void test_ignore_status__leading_spaces_are_significant(void) { static const char *test_files[] = { "empty_standard_repo/a.test", From f7c6795f48850eec94793d22b5fd621b61cd2466 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 10:20:35 +0200 Subject: [PATCH 3/7] path: only treat paths starting with '\' as absolute on Win32 Windows-based systems treat paths starting with '\' as absolute, either referring to the current drive's root (e.g. "\foo" might refer to "C:\foo") or to a network path (e.g. "\\host\foo"). On the other hand, (most?) systems that are not based on Win32 accept backslashes as valid characters that may be part of the filename, and thus we cannot treat them to identify absolute paths. Change the logic to only paths starting with '\' as absolute on the Win32 platform. Add tests to avoid regressions and document behaviour. --- src/path.c | 5 ++++- tests/path/core.c | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/path.c b/src/path.c index 197efb75a..af18edcae 100644 --- a/src/path.c +++ b/src/path.c @@ -276,9 +276,12 @@ int git_path_root(const char *path) while (path[offset] && path[offset] != '/' && path[offset] != '\\') offset++; } + + if (path[offset] == '\\') + return offset; #endif - if (path[offset] == '/' || path[offset] == '\\') + if (path[offset] == '/') return offset; return -1; /* Not a real error - signals that path is not rooted */ diff --git a/tests/path/core.c b/tests/path/core.c index bbcded083..3a68a9338 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -343,6 +343,16 @@ void test_path_core__join_unrooted(void) test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); +#ifdef GIT_WIN32 + /* Paths starting with '\\' are absolute */ + test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); + test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); +#else + /* Paths starting with '\\' are not absolute on non-Windows systems */ + test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); + test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); +#endif + /* Base is returned when it's provided and is the prefix */ test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); From eb146e58664428d9e23bc84e0e9f75db7924ae37 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 09:17:23 +0200 Subject: [PATCH 4/7] attr_file: properly handle escaped '\' when searching non-escaped spaces When parsing attributes, we need to search for the first unescaped whitespace character to determine where the pattern is to be cut off. The scan fails to account for the case where the escaping '\' character is itself escaped, though, and thus we would not recognize the cut-off point in patterns like "\\ ". Refactor the scanning loop to remember whether the last character was an escape character. If it was and the next character is a '\', too, then we will reset to non-escaped mode again. Thus, we now handle escaped whitespaces as well as escaped wildcards correctly. --- src/attr_file.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 673f9a406..5b1007ee5 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -587,6 +587,7 @@ int git_attr_fnmatch__parse( { const char *pattern, *scan; int slash_count, allow_space; + bool escaped; assert(spec && base && *base); @@ -623,28 +624,29 @@ int git_attr_fnmatch__parse( } slash_count = 0; + escaped = false; + /* Scan until a non-escaped whitespace. */ for (scan = pattern; *scan != '\0'; ++scan) { - /* - * Scan until a non-escaped whitespace: find a whitespace, then look - * one char backward to ensure that it's not prefixed by a `\`. - * Only look backward if we're not at the first position (`pattern`). - */ - if (git__isspace(*scan) && scan > pattern && *(scan - 1) != '\\') { - if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r')) - break; - } + char c = *scan; - if (*scan == '/') { + if (c == '\\' && !escaped) { + escaped = true; + continue; + } else if (git__isspace(c) && !escaped) { + if (!allow_space || (c != ' ' && c != '\t' && c != '\r')) + break; + } else if (c == '/') { spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; slash_count++; if (slash_count == 1 && pattern == scan) pattern++; - } - /* remember if we see an unescaped wildcard in pattern */ - else if (git__iswildcard(*scan) && - (scan == pattern || (*(scan - 1) != '\\'))) + } else if (git__iswildcard(c) && !escaped) { + /* remember if we see an unescaped wildcard in pattern */ spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; + } + + escaped = false; } *base = scan; From 10ac298c62b40a205e771f50f9171c900c0e50dd Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 11:12:42 +0200 Subject: [PATCH 5/7] attr_file: fix unescaping of escapes required for fnmatch When parsing attribute patterns, we will eventually unescape the parsed pattern. This is required because we require custom escapes for whitespace characters, as normally they are used to terminate the current pattern. Thing is, we don't only unescape those whitespace characters, but in fact all escaped sequences. So for example if the pattern was "\*", we unescape that to "*". As this is directly passed to fnmatch(3) later, fnmatch would treat it as a simple glob matching all files where it should instead only match a file with name "*". Fix the issue by unescaping spaces, only. Add a bunch of tests to exercise escape parsing. --- src/attr_file.c | 32 +++++++++++++++++++++-- tests/ignore/path.c | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 5b1007ee5..510ed10dd 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -574,6 +574,34 @@ static size_t trailing_space_length(const char *p, size_t len) return len - n; } +static size_t unescape_spaces(char *str) +{ + char *scan, *pos = str; + bool escaped = false; + + if (!str) + return 0; + + for (scan = str; *scan; scan++) { + if (!escaped && *scan == '\\') { + escaped = true; + continue; + } + + /* Only insert the escape character for escaped non-spaces */ + if (escaped && !git__isspace(*scan)) + *pos++ = '\\'; + + *pos++ = *scan; + escaped = false; + } + + if (pos != scan) + *pos = '\0'; + + return (pos - str); +} + /* * This will return 0 if the spec was filled out, * GIT_ENOTFOUND if the fnmatch does not require matching, or @@ -701,8 +729,8 @@ int git_attr_fnmatch__parse( *base = git__next_line(pattern); return -1; } else { - /* strip '\' that might have be used for internal whitespace */ - spec->length = git__unescape(spec->pattern); + /* strip '\' that might have been used for internal whitespace */ + spec->length = unescape_spaces(spec->pattern); /* TODO: convert remaining '\' into '/' for POSIX ??? */ } diff --git a/tests/ignore/path.c b/tests/ignore/path.c index 425a49ca9..0c22582ac 100644 --- a/tests/ignore/path.c +++ b/tests/ignore/path.c @@ -459,3 +459,65 @@ void test_ignore_path__negative_directory_rules_only_match_directories(void) assert_is_ignored(false, "src/A.keep"); assert_is_ignored(false, ".gitignore"); } + +void test_ignore_path__escaped_character(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\c\n"); + assert_is_ignored(true, "c"); + assert_is_ignored(false, "\\c"); +} + +void test_ignore_path__escaped_newline(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\nnewline\n" + ); + + assert_is_ignored(true, "\nnewline"); +} + +void test_ignore_path__escaped_glob(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\*\n"); + assert_is_ignored(true, "*"); + assert_is_ignored(false, "foo"); +} + +void test_ignore_path__escaped_comments(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "#foo\n" + "\\#bar\n" + "\\##baz\n" + "\\#\\\\#qux\n" + ); + + assert_is_ignored(false, "#foo"); + assert_is_ignored(true, "#bar"); + assert_is_ignored(false, "\\#bar"); + assert_is_ignored(true, "##baz"); + assert_is_ignored(false, "\\##baz"); + assert_is_ignored(true, "#\\#qux"); + assert_is_ignored(false, "##qux"); + assert_is_ignored(false, "\\##qux"); +} + +void test_ignore_path__escaped_slash(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\\\n" + "\\\\preceding\n" + "inter\\\\mittent\n" + "trailing\\\\\n" + ); + +#ifndef GIT_WIN32 + assert_is_ignored(true, "\\"); + assert_is_ignored(true, "\\preceding"); +#endif + assert_is_ignored(true, "inter\\mittent"); + assert_is_ignored(true, "trailing\\"); +} From b3b6a39d928cf4ecda54468cc5029013363654d7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 11:12:54 +0200 Subject: [PATCH 6/7] attr_file: account for escaped escapes when searching trailing space When determining the trailing space length, we need to honor whether spaces are escaped or not. Currently, we do not check whether the escape itself is escaped, though, which might generate an off-by-one in that case as we will simply treat the space as escaped. Fix this by checking whether the backslashes preceding the space are themselves escaped. --- src/attr_file.c | 18 +++++++++++++++--- tests/ignore/path.c | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 510ed10dd..08b6c3f29 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -565,10 +565,22 @@ void git_attr_path__free(git_attr_path *info) */ static size_t trailing_space_length(const char *p, size_t len) { - size_t n; + size_t n, i; for (n = len; n; n--) { - if ((p[n-1] != ' ' && p[n-1] != '\t') || - (n > 1 && p[n-2] == '\\')) + if (p[n-1] != ' ' && p[n-1] != '\t') + break; + + /* + * Count escape-characters before space. In case where it's an + * even number of escape characters, then the escape char itself + * is escaped and the whitespace is an unescaped whitespace. + * Otherwise, the last escape char is not escaped and the + * whitespace in an escaped whitespace. + */ + i = n; + while (i > 1 && p[i-2] == '\\') + i--; + if ((n - i) % 2) break; } return len - n; diff --git a/tests/ignore/path.c b/tests/ignore/path.c index 0c22582ac..bfed297c2 100644 --- a/tests/ignore/path.c +++ b/tests/ignore/path.c @@ -521,3 +521,20 @@ void test_ignore_path__escaped_slash(void) assert_is_ignored(true, "inter\\mittent"); assert_is_ignored(true, "trailing\\"); } + +void test_ignore_path__escaped_space(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo\\\\ \n" + "bar\\\\\\ \n"); + assert_is_ignored(true, "foo\\"); + assert_is_ignored(false, "foo\\ "); + assert_is_ignored(false, "foo\\\\ "); + assert_is_ignored(false, "foo\\\\"); + assert_is_ignored(true, "bar\\ "); + assert_is_ignored(false, "bar\\\\"); + assert_is_ignored(false, "bar\\\\ "); + assert_is_ignored(false, "bar\\\\\\"); + assert_is_ignored(false, "bar\\\\\\ "); +} From 3b517351d06d37d04d1ace80be04c84d225fa793 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2019 10:13:34 +0200 Subject: [PATCH 7/7] attr_file: remove invalid TODO comment In our attributes pattern parsing code, we have a comment that states we might have to convert '\' characters to '/' to have proper POSIX paths. But in fact, '\' characters are valid inside the string and act as escape mechanism for various characters, which is why we never want to convert those to POSIX directory separators. Furthermore, gitignore patterns are specified to only treat '/' as directory separators. Remove the comment to avoid future confusion. --- src/attr_file.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/attr_file.c b/src/attr_file.c index 08b6c3f29..f01a743d2 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -743,7 +743,6 @@ int git_attr_fnmatch__parse( } else { /* strip '\' that might have been used for internal whitespace */ spec->length = unescape_spaces(spec->pattern); - /* TODO: convert remaining '\' into '/' for POSIX ??? */ } return 0;