diff options
author | Philip Kelley <phkelley@hotmail.com> | 2012-10-16 08:34:28 -0700 |
---|---|---|
committer | Philip Kelley <phkelley@hotmail.com> | 2012-10-16 08:34:28 -0700 |
commit | 9e37305aad734efb83f22b2e292a7591bbf49f15 (patch) | |
tree | 91b7498ffd8f292bac418c9dc0067aead5a435fa | |
parent | ebb867558e734d8c053dd1a292ff9861197bdc4d (diff) | |
parent | 52032ae53689ac37350f6af3bf1834122e4b3cf0 (diff) | |
download | libgit2-9e37305aad734efb83f22b2e292a7591bbf49f15.tar.gz |
Merge pull request #984 from arrbee/fix-fnmatch-and-ignore
Fix single file ignores
-rw-r--r-- | include/git2/ignore.h | 9 | ||||
-rw-r--r-- | include/git2/status.h | 10 | ||||
-rw-r--r-- | src/attr_file.h | 8 | ||||
-rw-r--r-- | src/fnmatch.c (renamed from src/compat/fnmatch.c) | 20 | ||||
-rw-r--r-- | src/fnmatch.h (renamed from src/compat/fnmatch.h) | 7 | ||||
-rw-r--r-- | src/ignore.c | 69 | ||||
-rw-r--r-- | src/posix.h | 1 | ||||
-rw-r--r-- | src/status.c | 23 | ||||
-rw-r--r-- | src/unix/posix.h | 7 | ||||
-rw-r--r-- | src/win32/posix.h | 1 | ||||
-rw-r--r-- | tests-clar/diff/iterator.c | 6 | ||||
-rw-r--r-- | tests-clar/resources/attr/gitignore | 1 | ||||
-rw-r--r-- | tests-clar/resources/attr/sub/ign | 1 | ||||
-rw-r--r-- | tests-clar/resources/attr/sub/ign/file | 1 | ||||
-rw-r--r-- | tests-clar/resources/attr/sub/ign/sub/file | 1 | ||||
-rw-r--r-- | tests-clar/status/ignore.c | 113 |
16 files changed, 236 insertions, 42 deletions
diff --git a/include/git2/ignore.h b/include/git2/ignore.h index f7e04e881..964a108ce 100644 --- a/include/git2/ignore.h +++ b/include/git2/ignore.h @@ -54,9 +54,12 @@ GIT_EXTERN(int) git_ignore_clear_internal_rules( /** * Test if the ignore rules apply to a given path. * - * This function simply checks the ignore rules to see if they would apply - * to the given file. This indicates if the file would be ignored regardless - * of whether the file is already in the index or commited to the repository. + * This function checks the ignore rules to see if they would apply to the + * given file. This indicates if the file would be ignored regardless of + * whether the file is already in the index or commited to the repository. + * + * One way to think of this is if you were to do "git add ." on the + * directory containing the file, would it be added or not? * * @param ignored boolean returning 0 if the file is not ignored, 1 if it is * @param repo a repository object diff --git a/include/git2/status.h b/include/git2/status.h index cc94d7680..0dd98596e 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -146,10 +146,12 @@ GIT_EXTERN(int) git_status_file( /** * Test if the ignore rules apply to a given file. * - * This function simply checks the ignore rules to see if they would apply - * to the given file. Unlike git_status_file(), this indicates if the file - * would be ignored regardless of whether the file is already in the index - * or in the repository. + * This function checks the ignore rules to see if they would apply to the + * given file. This indicates if the file would be ignored regardless of + * whether the file is already in the index or commited to the repository. + * + * One way to think of this is if you were to do "git add ." on the + * directory containing the file, would it be added or not? * * @param ignored boolean returning 0 if the file is not ignored, 1 if it is * @param repo a repository object diff --git a/src/attr_file.h b/src/attr_file.h index b28d8a02b..877daf306 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -71,10 +71,10 @@ typedef struct { } git_attr_file; typedef struct { - git_buf full; - const char *path; - const char *basename; - int is_dir; + git_buf full; + char *path; + char *basename; + int is_dir; } git_attr_path; typedef enum { diff --git a/src/compat/fnmatch.c b/src/fnmatch.c index 835d811bc..f394274da 100644 --- a/src/compat/fnmatch.c +++ b/src/fnmatch.c @@ -24,13 +24,16 @@ static int rangematch(const char *, char, int, char **); -int -p_fnmatch(const char *pattern, const char *string, int flags) +static int +p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs) { const char *stringstart; char *newp; char c, test; + if (recurs-- == 0) + return FNM_NORES; + for (stringstart = string;;) switch (c = *pattern++) { case EOS: @@ -75,8 +78,11 @@ p_fnmatch(const char *pattern, const char *string, int flags) /* General case, use recursion. */ while ((test = *string) != EOS) { - if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) - return (0); + int e; + + e = p_fnmatchx(pattern, string, flags & ~FNM_PERIOD, recurs); + if (e != FNM_NOMATCH) + return e; if (test == '/' && (flags & FNM_PATHNAME)) break; ++string; @@ -178,3 +184,9 @@ rangematch(const char *pattern, char test, int flags, char **newp) return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); } +int +p_fnmatch(const char *pattern, const char *string, int flags) +{ + return p_fnmatchx(pattern, string, flags, 64); +} + diff --git a/src/compat/fnmatch.h b/src/fnmatch.h index 7faef09b3..913efd1a0 100644 --- a/src/compat/fnmatch.h +++ b/src/fnmatch.h @@ -11,12 +11,13 @@ #define FNM_NOMATCH 1 /* Match failed. */ #define FNM_NOSYS 2 /* Function not supported (unused). */ +#define FNM_NORES 3 /* Out of resources */ -#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ -#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ #define FNM_PERIOD 0x04 /* Period must be matched by period. */ #define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ -#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ #define FNM_IGNORECASE FNM_CASEFOLD #define FNM_FILE_NAME FNM_PATHNAME diff --git a/src/ignore.c b/src/ignore.c index e711be206..6a377e60d 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -277,15 +277,76 @@ int git_ignore_clear_internal_rules( int git_ignore_path_is_ignored( int *ignored, git_repository *repo, - const char *path) + const char *pathname) { int error; + const char *workdir; + git_attr_path path; + char *tail, *end; + bool full_is_dir; git_ignores ignores; + unsigned int i; + git_attr_file *file; - if (git_ignore__for_path(repo, path, &ignores) < 0) - return -1; + assert(ignored && pathname); + + workdir = repo ? git_repository_workdir(repo) : NULL; + + if ((error = git_attr_path__init(&path, pathname, workdir)) < 0) + return error; + + tail = path.path; + end = &path.full.ptr[path.full.size]; + full_is_dir = path.is_dir; - error = git_ignore__lookup(&ignores, path, ignored); + while (1) { + /* advance to next component of path */ + path.basename = tail; + + while (tail < end && *tail != '/') tail++; + *tail = '\0'; + + path.full.size = (tail - path.full.ptr); + path.is_dir = (tail == end) ? full_is_dir : true; + + /* update ignores for new path fragment */ + if (path.basename == path.path) + error = git_ignore__for_path(repo, path.path, &ignores); + else + error = git_ignore__push_dir(&ignores, path.basename); + if (error < 0) + break; + + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules( + &ignores.ign_internal->rules, &path, ignored)) + goto cleanup; + + /* next process files in the path */ + git_vector_foreach(&ignores.ign_path, i, file) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores.ign_global, i, file) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + /* if we found no rules before reaching the end, we're done */ + if (tail == end) + break; + + /* reinstate divider in path */ + *tail = '/'; + while (*tail == '/') tail++; + } + + *ignored = 0; + +cleanup: + git_attr_path__free(&path); git_ignore__free(&ignores); return error; } diff --git a/src/posix.h b/src/posix.h index 71bb82283..d565dc11f 100644 --- a/src/posix.h +++ b/src/posix.h @@ -10,6 +10,7 @@ #include "common.h" #include <fcntl.h> #include <time.h> +#include "fnmatch.h" #ifndef S_IFGITLINK #define S_IFGITLINK 0160000 diff --git a/src/status.c b/src/status.c index 3a0ed075f..a37653db4 100644 --- a/src/status.c +++ b/src/status.c @@ -86,6 +86,10 @@ int git_status_foreach_ext( assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + if (show != GIT_STATUS_SHOW_INDEX_ONLY && + (err = git_repository__ensure_not_bare(repo, "status")) < 0) + return err; + if ((err = git_repository_head_tree(&head, repo)) < 0) return err; @@ -245,9 +249,22 @@ int git_status_file( error = GIT_EAMBIGUOUS; if (!error && !sfi.count) { - giterr_set(GITERR_INVALID, - "Attempt to get status of nonexistent file '%s'", path); - error = GIT_ENOTFOUND; + git_buf full = GIT_BUF_INIT; + + /* if the file actually exists and we still did not get a callback + * for it, then it must be contained inside an ignored directory, so + * mark it as such instead of generating an error. + */ + if (!git_buf_joinpath(&full, git_repository_workdir(repo), path) && + git_path_exists(full.ptr)) + sfi.status = GIT_STATUS_IGNORED; + else { + giterr_set(GITERR_INVALID, + "Attempt to get status of nonexistent file '%s'", path); + error = GIT_ENOTFOUND; + } + + git_buf_free(&full); } *status_flags = sfi.status; diff --git a/src/unix/posix.h b/src/unix/posix.h index 25038c827..bcd800301 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -7,13 +7,6 @@ #ifndef INCLUDE_posix__w32_h__ #define INCLUDE_posix__w32_h__ -#if !defined(__sun) && !defined(__amigaos4__) -# include <fnmatch.h> -# define p_fnmatch(p, s, f) fnmatch(p, s, f) -#else -# include "compat/fnmatch.h" -#endif - #include <stdio.h> #define p_lstat(p,b) lstat(p,b) diff --git a/src/win32/posix.h b/src/win32/posix.h index da46cf514..80dcca5c1 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -8,7 +8,6 @@ #define INCLUDE_posix__w32_h__ #include "common.h" -#include "compat/fnmatch.h" #include "utf-conv.h" GIT_INLINE(int) p_link(const char *old, const char *new) diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index c27d3fa6c..cca6450d2 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -451,13 +451,13 @@ static void workdir_iterator_test( git_iterator_free(i); - cl_assert(count == expected_count); - cl_assert(count_all == expected_count + expected_ignores); + cl_assert_equal_i(expected_count,count); + cl_assert_equal_i(expected_count + expected_ignores, count_all); } void test_diff_iterator__workdir_0(void) { - workdir_iterator_test("attr", NULL, NULL, 25, 2, NULL, "ign"); + workdir_iterator_test("attr", NULL, NULL, 27, 1, NULL, "ign"); } static const char *status_paths[] = { diff --git a/tests-clar/resources/attr/gitignore b/tests-clar/resources/attr/gitignore index 546d48f3a..192967012 100644 --- a/tests-clar/resources/attr/gitignore +++ b/tests-clar/resources/attr/gitignore @@ -1,3 +1,2 @@ -sub ign dir/ diff --git a/tests-clar/resources/attr/sub/ign b/tests-clar/resources/attr/sub/ign deleted file mode 100644 index 592fd2594..000000000 --- a/tests-clar/resources/attr/sub/ign +++ /dev/null @@ -1 +0,0 @@ -ignore me diff --git a/tests-clar/resources/attr/sub/ign/file b/tests-clar/resources/attr/sub/ign/file new file mode 100644 index 000000000..4dcd992e1 --- /dev/null +++ b/tests-clar/resources/attr/sub/ign/file @@ -0,0 +1 @@ +in ignored dir diff --git a/tests-clar/resources/attr/sub/ign/sub/file b/tests-clar/resources/attr/sub/ign/sub/file new file mode 100644 index 000000000..88aca0164 --- /dev/null +++ b/tests-clar/resources/attr/sub/ign/sub/file @@ -0,0 +1 @@ +below ignored dir diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c index 68dc652f5..bd74b9740 100644 --- a/tests-clar/status/ignore.c +++ b/tests-clar/status/ignore.c @@ -22,21 +22,25 @@ void test_status_ignore__0(void) const char *path; int expected; } test_cases[] = { - /* patterns "sub" and "ign" from .gitignore */ + /* pattern "ign" from .gitignore */ { "file", 0 }, { "ign", 1 }, - { "sub", 1 }, + { "sub", 0 }, { "sub/file", 0 }, { "sub/ign", 1 }, - { "sub/sub", 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", 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; @@ -172,6 +176,61 @@ 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) +{ + status_entry_single st; + int ignored; + git_status_options opts; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/ignore_me", "I'm going to be ignored!"); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert_equal_i(2, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&st.status, g_repo, "ignore_me")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "ignore_me")); + cl_assert(ignored); + + + /* So, interestingly, as per the comment in diff_from_iterators() the + * following file is ignored, but in a way so that it does not show up + * in status even if INCLUDE_IGNORED is used. This actually matches + * core git's behavior - if you follow these steps and try running "git + * status -uall --ignored" then the following file and directory will + * not show up in the output at all. + */ + + cl_git_pass( + git_futils_mkdir_r("empty_standard_repo/test/ignore_me", NULL, 0775)); + cl_git_mkfile( + "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert_equal_i(2, st.count); + + cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass( + git_status_should_ignore(&ignored, g_repo, "test/ignore_me/file")); + cl_assert(ignored); +} + void test_status_ignore__adding_internal_ignores(void) { int ignored; @@ -234,3 +293,49 @@ void test_status_ignore__add_internal_as_first_thing(void) cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); cl_assert(!ignored); } + +void test_status_ignore__internal_ignores_inside_deep_paths(void) +{ + int ignored; + const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_ignore_add_rule(g_repo, add_me)); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "Debug")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/Debug")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "really/Debug/this/file")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "Debug/what/I/say")); + cl_assert(ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/NoDebug")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "NoDebug/this")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "please/NoDebug/this")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deep")); + cl_assert(ignored); + /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "and/this/is/deep")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deep/too")); + cl_assert(ignored); + /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "but/this/is/deep/and/ignored")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/not/deep")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "is/this/not/as/deep")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "this/is/deepish")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "xthis/is/deep")); + cl_assert(!ignored); +} |