From 57534ee77d22e725d971ee89c77dc6aad61c573f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 21 Sep 2015 16:56:15 +0700 Subject: dir.c: don't exclude whole dir prematurely if neg pattern may match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If there is a pattern "!foo/bar", this patch makes it not exclude "foo" right away. This gives us a chance to examine "foo" and re-include "foo/bar". In order for it to detect that the directory under examination should not be excluded right away, in other words it is a parent directory of a negative pattern, the "directory path" of the negative pattern must be literal. Patterns like "!f?o/bar" can't stop "foo" from being excluded. Basename matching (i.e. "no slashes in the pattern") or must-be-dir matching (i.e. "trailing slash in the pattern") does not work well with this. For example, if we descend in "foo" and are examining "foo/abc", current code for "foo/" pattern will check if path "foo/abc", not "foo", is a directory. The same problem with basename matching. These may need big code reorg to make it work. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) (limited to 'dir.c') diff --git a/dir.c b/dir.c index 4893181a0c..894027c0cd 100644 --- a/dir.c +++ b/dir.c @@ -733,6 +733,25 @@ int match_pathname(const char *pathname, int pathlen, */ if (!patternlen && !namelen) return 1; + /* + * This can happen when we ignore some exclude rules + * on directories in other to see if negative rules + * may match. E.g. + * + * /abc + * !/abc/def/ghi + * + * The pattern of interest is "/abc". On the first + * try, we should match path "abc" with this pattern + * in the "if" statement right above, but the caller + * ignores it. + * + * On the second try with paths within "abc", + * e.g. "abc/xyz", we come here and try to match it + * with "/abc". + */ + if (!patternlen && namelen && *name == '/') + return 1; } return fnmatch_icase_mem(pattern, patternlen, @@ -740,6 +759,48 @@ int match_pathname(const char *pathname, int pathlen, WM_PATHNAME) == 0; } +/* + * Return non-zero if pathname is a directory and an ancestor of the + * literal path in a (negative) pattern. This is used to keep + * descending in "foo" and "foo/bar" when the pattern is + * "!foo/bar/.gitignore". "foo/notbar" will not be descended however. + */ +static int match_neg_path(const char *pathname, int pathlen, int *dtype, + const char *base, int baselen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR)); + + if (*dtype == DT_UNKNOWN) + *dtype = get_dtype(NULL, pathname, pathlen); + if (*dtype != DT_DIR) + return 0; + + if (*pattern == '/') { + pattern++; + patternlen--; + prefix--; + } + + if (baselen) { + if (((pathlen < baselen && base[pathlen] == '/') || + pathlen == baselen) && + !strncmp_icase(pathname, base, pathlen)) + return 1; + pathname += baselen + 1; + pathlen -= baselen + 1; + } + + + if (prefix && + ((pathlen < prefix && pattern[pathlen] == '/') && + !strncmp_icase(pathname, pattern, pathlen))) + return 1; + + return 0; +} + /* * Scan the given exclude list in reverse to see whether pathname * should be ignored. The first match (i.e. the last on the list), if @@ -753,7 +814,7 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, struct exclude_list *el) { struct exclude *exc = NULL; /* undecided */ - int i; + int i, matched_negative_path = 0; if (!el->nr) return NULL; /* undefined */ @@ -788,7 +849,18 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, exc = x; break; } + + if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path && + match_neg_path(pathname, pathlen, dtype, x->base, + x->baselen ? x->baselen - 1 : 0, + exclude, prefix, x->patternlen, x->flags)) + matched_negative_path = 1; } + if (exc && + !(exc->flags & EXC_FLAG_NEGATIVE) && + !(exc->flags & EXC_FLAG_NODIR) && + matched_negative_path) + exc = NULL; return exc; } -- cgit v1.2.1