From 824d5e4d260fc740dbe9251008439b9e58db5f19 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 11 Oct 2012 11:58:00 -0700 Subject: Always use internal fnmatch, not system --- src/compat/fnmatch.c | 180 --------------------------------------------------- src/compat/fnmatch.h | 27 -------- src/fnmatch.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/fnmatch.h | 27 ++++++++ src/posix.h | 1 + src/unix/posix.h | 7 -- src/win32/posix.h | 1 - 7 files changed, 208 insertions(+), 215 deletions(-) delete mode 100644 src/compat/fnmatch.c delete mode 100644 src/compat/fnmatch.h create mode 100644 src/fnmatch.c create mode 100644 src/fnmatch.h diff --git a/src/compat/fnmatch.c b/src/compat/fnmatch.c deleted file mode 100644 index 835d811bc..000000000 --- a/src/compat/fnmatch.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -/* - * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. - * Compares a filename or pathname to a pattern. - */ - -#include -#include -#include - -#include "fnmatch.h" - -#define EOS '\0' - -#define RANGE_MATCH 1 -#define RANGE_NOMATCH 0 -#define RANGE_ERROR (-1) - -static int rangematch(const char *, char, int, char **); - -int -p_fnmatch(const char *pattern, const char *string, int flags) -{ - const char *stringstart; - char *newp; - char c, test; - - for (stringstart = string;;) - switch (c = *pattern++) { - case EOS: - if ((flags & FNM_LEADING_DIR) && *string == '/') - return (0); - return (*string == EOS ? 0 : FNM_NOMATCH); - case '?': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - ++string; - break; - case '*': - c = *pattern; - /* Collapse multiple stars. */ - while (c == '*') - c = *++pattern; - - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - /* Optimize for pattern with * at end or before /. */ - if (c == EOS) { - if (flags & FNM_PATHNAME) - return ((flags & FNM_LEADING_DIR) || - strchr(string, '/') == NULL ? - 0 : FNM_NOMATCH); - else - return (0); - } else if (c == '/' && (flags & FNM_PATHNAME)) { - if ((string = strchr(string, '/')) == NULL) - return (FNM_NOMATCH); - break; - } - - /* General case, use recursion. */ - while ((test = *string) != EOS) { - if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) - return (0); - if (test == '/' && (flags & FNM_PATHNAME)) - break; - ++string; - } - return (FNM_NOMATCH); - case '[': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - switch (rangematch(pattern, *string, flags, &newp)) { - case RANGE_ERROR: - /* not a good range, treat as normal text */ - goto normal; - case RANGE_MATCH: - pattern = newp; - break; - case RANGE_NOMATCH: - return (FNM_NOMATCH); - } - ++string; - break; - case '\\': - if (!(flags & FNM_NOESCAPE)) { - if ((c = *pattern++) == EOS) { - c = '\\'; - --pattern; - } - } - /* FALLTHROUGH */ - default: - normal: - if (c != *string && !((flags & FNM_CASEFOLD) && - (tolower((unsigned char)c) == - tolower((unsigned char)*string)))) - return (FNM_NOMATCH); - ++string; - break; - } - /* NOTREACHED */ -} - -static int -rangematch(const char *pattern, char test, int flags, char **newp) -{ - int negate, ok; - char c, c2; - - /* - * A bracket expression starting with an unquoted circumflex - * character produces unspecified results (IEEE 1003.2-1992, - * 3.13.2). This implementation treats it like '!', for - * consistency with the regular expression syntax. - * J.T. Conklin (conklin@ngai.kaleida.com) - */ - if ((negate = (*pattern == '!' || *pattern == '^')) != 0) - ++pattern; - - if (flags & FNM_CASEFOLD) - test = (char)tolower((unsigned char)test); - - /* - * A right bracket shall lose its special meaning and represent - * itself in a bracket expression if it occurs first in the list. - * -- POSIX.2 2.8.3.2 - */ - ok = 0; - c = *pattern++; - do { - if (c == '\\' && !(flags & FNM_NOESCAPE)) - c = *pattern++; - if (c == EOS) - return (RANGE_ERROR); - if (c == '/' && (flags & FNM_PATHNAME)) - return (RANGE_NOMATCH); - if ((flags & FNM_CASEFOLD)) - c = (char)tolower((unsigned char)c); - if (*pattern == '-' - && (c2 = *(pattern+1)) != EOS && c2 != ']') { - pattern += 2; - if (c2 == '\\' && !(flags & FNM_NOESCAPE)) - c2 = *pattern++; - if (c2 == EOS) - return (RANGE_ERROR); - if (flags & FNM_CASEFOLD) - c2 = (char)tolower((unsigned char)c2); - if (c <= test && test <= c2) - ok = 1; - } else if (c == test) - ok = 1; - } while ((c = *pattern++) != ']'); - - *newp = (char *)pattern; - return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); -} - diff --git a/src/compat/fnmatch.h b/src/compat/fnmatch.h deleted file mode 100644 index 7faef09b3..000000000 --- a/src/compat/fnmatch.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_fnmatch__compat_h__ -#define INCLUDE_fnmatch__compat_h__ - -#include "common.h" - -#define FNM_NOMATCH 1 /* Match failed. */ -#define FNM_NOSYS 2 /* Function not supported (unused). */ - -#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 / after Imatch. */ -#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ - -#define FNM_IGNORECASE FNM_CASEFOLD -#define FNM_FILE_NAME FNM_PATHNAME - -extern int p_fnmatch(const char *pattern, const char *string, int flags); - -#endif /* _FNMATCH_H */ - diff --git a/src/fnmatch.c b/src/fnmatch.c new file mode 100644 index 000000000..835d811bc --- /dev/null +++ b/src/fnmatch.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include +#include +#include + +#include "fnmatch.h" + +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int rangematch(const char *, char, int, char **); + +int +p_fnmatch(const char *pattern, const char *string, int flags) +{ + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || + strchr(string, '/') == NULL ? + 0 : FNM_NOMATCH); + else + return (0); + } else if (c == '/' && (flags & FNM_PATHNAME)) { + if ((string = strchr(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && (flags & FNM_PATHNAME)) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + /* not a good range, treat as normal text */ + goto normal; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + normal: + if (c != *string && !((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string)))) + return (FNM_NOMATCH); + ++string; + break; + } + /* NOTREACHED */ +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^')) != 0) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = (char)tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + if ((flags & FNM_CASEFOLD)) + c = (char)tolower((unsigned char)c); + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + if (flags & FNM_CASEFOLD) + c2 = (char)tolower((unsigned char)c2); + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char *)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} + diff --git a/src/fnmatch.h b/src/fnmatch.h new file mode 100644 index 000000000..7faef09b3 --- /dev/null +++ b/src/fnmatch.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fnmatch__compat_h__ +#define INCLUDE_fnmatch__compat_h__ + +#include "common.h" + +#define FNM_NOMATCH 1 /* Match failed. */ +#define FNM_NOSYS 2 /* Function not supported (unused). */ + +#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 / after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ + +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +extern int p_fnmatch(const char *pattern, const char *string, int flags); + +#endif /* _FNMATCH_H */ + 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 #include +#include "fnmatch.h" #ifndef S_IFGITLINK #define S_IFGITLINK 0160000 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 -# define p_fnmatch(p, s, f) fnmatch(p, s, f) -#else -# include "compat/fnmatch.h" -#endif - #include #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) -- cgit v1.2.1 From d5a51910678f8aea2b7efe077efc678141762dfc Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 11 Oct 2012 13:39:53 -0700 Subject: Import DOS fix for fnmatch Because fnmatch uses recursion, there were some input sequences that cause seriously degenerate behavior. This imports a fix that imposes a max recursion limiter to avoid the worst of it. --- src/fnmatch.c | 20 ++++++++++++++++---- src/fnmatch.h | 7 ++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/fnmatch.c b/src/fnmatch.c index 835d811bc..f394274da 100644 --- a/src/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/fnmatch.h b/src/fnmatch.h index 7faef09b3..913efd1a0 100644 --- a/src/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 / 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 -- cgit v1.2.1 From 52032ae53689ac37350f6af3bf1834122e4b3cf0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 15 Oct 2012 12:48:43 -0700 Subject: Fix single-file ignore checks To answer if a single given file should be ignored, the path to that file has to be processed progressively checking that there are no intermediate ignored directories in getting to the file in question. This enables that, fixing the broken old behavior, and adds tests to exercise various ignore situations. --- include/git2/ignore.h | 9 ++- include/git2/status.h | 10 ++- src/attr_file.h | 8 +- src/ignore.c | 69 +++++++++++++++++- src/status.c | 23 +++++- tests-clar/diff/iterator.c | 6 +- tests-clar/resources/attr/gitignore | 1 - tests-clar/resources/attr/sub/ign | 1 - tests-clar/resources/attr/sub/ign/file | 1 + tests-clar/resources/attr/sub/ign/sub/file | 1 + tests-clar/status/ignore.c | 113 ++++++++++++++++++++++++++++- 11 files changed, 215 insertions(+), 27 deletions(-) delete mode 100644 tests-clar/resources/attr/sub/ign create mode 100644 tests-clar/resources/attr/sub/ign/file create mode 100644 tests-clar/resources/attr/sub/ign/sub/file 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/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/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/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); +} -- cgit v1.2.1