diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 256 |
1 files changed, 181 insertions, 75 deletions
@@ -34,10 +34,33 @@ int fnmatch_icase(const char *pattern, const char *string, int flags) return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0)); } +inline int git_fnmatch(const char *pattern, const char *string, + int flags, int prefix) +{ + int fnm_flags = 0; + if (flags & GFNM_PATHNAME) + fnm_flags |= FNM_PATHNAME; + if (prefix > 0) { + if (strncmp(pattern, string, prefix)) + return FNM_NOMATCH; + pattern += prefix; + string += prefix; + } + if (flags & GFNM_ONESTAR) { + int pattern_len = strlen(++pattern); + int string_len = strlen(string); + return string_len < pattern_len || + strcmp(pattern, + string + string_len - pattern_len); + } + return fnmatch(pattern, string, fnm_flags); +} + static size_t common_prefix_len(const char **pathspec) { const char *n, *first; size_t max = 0; + int literal = limit_pathspec_to_literal(); if (!pathspec) return max; @@ -47,7 +70,7 @@ static size_t common_prefix_len(const char **pathspec) size_t i, len = 0; for (i = 0; first == n || i < max; i++) { char c = n[i]; - if (!c || c != first[i] || is_glob_special(c)) + if (!c || c != first[i] || (!literal && is_glob_special(c))) break; if (c == '/') len = i + 1; @@ -117,6 +140,7 @@ int within_depth(const char *name, int namelen, static int match_one(const char *match, const char *name, int namelen) { int matchlen; + int literal = limit_pathspec_to_literal(); /* If the match was just the prefix, we matched */ if (!*match) @@ -126,7 +150,7 @@ static int match_one(const char *match, const char *name, int namelen) for (;;) { unsigned char c1 = tolower(*match); unsigned char c2 = tolower(*name); - if (c1 == '\0' || is_glob_special(c1)) + if (c1 == '\0' || (!literal && is_glob_special(c1))) break; if (c1 != c2) return 0; @@ -138,7 +162,7 @@ static int match_one(const char *match, const char *name, int namelen) for (;;) { unsigned char c1 = *match; unsigned char c2 = *name; - if (c1 == '\0' || is_glob_special(c1)) + if (c1 == '\0' || (!literal && is_glob_special(c1))) break; if (c1 != c2) return 0; @@ -148,14 +172,16 @@ static int match_one(const char *match, const char *name, int namelen) } } - /* * If we don't match the matchstring exactly, * we need to match by fnmatch */ matchlen = strlen(match); - if (strncmp_icase(match, name, matchlen)) + if (strncmp_icase(match, name, matchlen)) { + if (literal) + return 0; return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0; + } if (namelen == matchlen) return MATCHED_EXACTLY; @@ -230,7 +256,10 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, return MATCHED_RECURSIVELY; } - if (item->use_wildcard && !fnmatch(match, name, 0)) + if (item->nowildcard_len < item->len && + !git_fnmatch(match, name, + item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + item->nowildcard_len - prefix)) return MATCHED_FNMATCH; return 0; @@ -308,42 +337,69 @@ static int no_wildcard(const char *string) return string[simple_length(string)] == '\0'; } +void parse_exclude_pattern(const char **pattern, + int *patternlen, + int *flags, + int *nowildcardlen) +{ + const char *p = *pattern; + size_t i, len; + + *flags = 0; + if (*p == '!') { + *flags |= EXC_FLAG_NEGATIVE; + p++; + } + len = strlen(p); + if (len && p[len - 1] == '/') { + len--; + *flags |= EXC_FLAG_MUSTBEDIR; + } + for (i = 0; i < len; i++) { + if (p[i] == '/') + break; + } + if (i == len) + *flags |= EXC_FLAG_NODIR; + *nowildcardlen = simple_length(p); + /* + * we should have excluded the trailing slash from 'p' too, + * but that's one more allocation. Instead just make sure + * nowildcardlen does not exceed real patternlen + */ + if (*nowildcardlen > len) + *nowildcardlen = len; + if (*p == '*' && no_wildcard(p + 1)) + *flags |= EXC_FLAG_ENDSWITH; + *pattern = p; + *patternlen = len; +} + void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { struct exclude *x; - size_t len; - int to_exclude = 1; - int flags = 0; + int patternlen; + int flags; + int nowildcardlen; - if (*string == '!') { - to_exclude = 0; - string++; - } - len = strlen(string); - if (len && string[len - 1] == '/') { + parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen); + if (flags & EXC_FLAG_MUSTBEDIR) { char *s; - x = xmalloc(sizeof(*x) + len); + x = xmalloc(sizeof(*x) + patternlen + 1); s = (char *)(x+1); - memcpy(s, string, len - 1); - s[len - 1] = '\0'; - string = s; + memcpy(s, string, patternlen); + s[patternlen] = '\0'; x->pattern = s; - flags = EXC_FLAG_MUSTBEDIR; } else { x = xmalloc(sizeof(*x)); x->pattern = string; } - x->to_exclude = to_exclude; - x->patternlen = strlen(string); + x->patternlen = patternlen; + x->nowildcardlen = nowildcardlen; x->base = base; x->baselen = baselen; x->flags = flags; - if (!strchr(string, '/')) - x->flags |= EXC_FLAG_NODIR; - x->nowildcardlen = simple_length(string); - if (*string == '*' && no_wildcard(string+1)) - x->flags |= EXC_FLAG_ENDSWITH; ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); which->excludes[which->nr++] = x; } @@ -505,6 +561,72 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) dir->basebuf[baselen] = '\0'; } +int match_basename(const char *basename, int basenamelen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + if (prefix == patternlen) { + if (!strcmp_icase(pattern, basename)) + return 1; + } else if (flags & EXC_FLAG_ENDSWITH) { + if (patternlen - 1 <= basenamelen && + !strcmp_icase(pattern + 1, + basename + basenamelen - patternlen + 1)) + return 1; + } else { + if (fnmatch_icase(pattern, basename, 0) == 0) + return 1; + } + return 0; +} + +int match_pathname(const char *pathname, int pathlen, + const char *base, int baselen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + const char *name; + int namelen; + + /* + * match with FNM_PATHNAME; the pattern has base implicitly + * in front of it. + */ + if (*pattern == '/') { + pattern++; + prefix--; + } + + /* + * baselen does not count the trailing slash. base[] may or + * may not end with a trailing slash though. + */ + if (pathlen < baselen + 1 || + (baselen && pathname[baselen] != '/') || + strncmp_icase(pathname, base, baselen)) + return 0; + + namelen = baselen ? pathlen - baselen - 1 : pathlen; + name = pathname + pathlen - namelen; + + if (prefix) { + /* + * if the non-wildcard part is longer than the + * remaining pathname, surely it cannot match. + */ + if (prefix > namelen) + return 0; + + if (strncmp_icase(pattern, name, prefix)) + return 0; + pattern += prefix; + name += prefix; + namelen -= prefix; + } + + return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0; +} + /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ @@ -519,9 +641,9 @@ int excluded_from_list(const char *pathname, for (i = el->nr - 1; 0 <= i; i--) { struct exclude *x = el->excludes[i]; - const char *name, *exclude = x->pattern; - int to_exclude = x->to_exclude; - int namelen, prefix = x->nowildcardlen; + const char *exclude = x->pattern; + int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1; + int prefix = x->nowildcardlen; if (x->flags & EXC_FLAG_MUSTBEDIR) { if (*dtype == DT_UNKNOWN) @@ -531,51 +653,18 @@ int excluded_from_list(const char *pathname, } if (x->flags & EXC_FLAG_NODIR) { - /* match basename */ - if (prefix == x->patternlen) { - if (!strcmp_icase(exclude, basename)) - return to_exclude; - } else if (x->flags & EXC_FLAG_ENDSWITH) { - if (x->patternlen - 1 <= pathlen && - !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1)) - return to_exclude; - } else { - if (fnmatch_icase(exclude, basename, 0) == 0) - return to_exclude; - } + if (match_basename(basename, + pathlen - (basename - pathname), + exclude, prefix, x->patternlen, + x->flags)) + return to_exclude; continue; } - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly in front of it. - */ - if (*exclude == '/') { - exclude++; - prefix--; - } - - if (pathlen < x->baselen || - (x->baselen && pathname[x->baselen-1] != '/') || - strncmp_icase(pathname, x->base, x->baselen)) - continue; - - namelen = x->baselen ? pathlen - x->baselen : pathlen; - name = pathname + pathlen - namelen; - - /* if the non-wildcard part is longer than the - remaining pathname, surely it cannot match */ - if (prefix > namelen) - continue; - - if (prefix) { - if (strncmp_icase(exclude, name, prefix)) - continue; - exclude += prefix; - name += prefix; - namelen -= prefix; - } - - if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME)) + assert(x->baselen == 0 || x->base[x->baselen - 1] == '/'); + if (match_pathname(pathname, pathlen, + x->base, x->baselen ? x->baselen - 1 : 0, + exclude, prefix, x->patternlen, x->flags)) return to_exclude; } return -1; /* undecided */ @@ -1369,9 +1458,18 @@ int init_pathspec(struct pathspec *pathspec, const char **paths) item->match = path; item->len = strlen(path); - item->use_wildcard = !no_wildcard(path); - if (item->use_wildcard) - pathspec->has_wildcard = 1; + item->flags = 0; + if (limit_pathspec_to_literal()) { + item->nowildcard_len = item->len; + } else { + item->nowildcard_len = simple_length(path); + if (item->nowildcard_len < item->len) { + pathspec->has_wildcard = 1; + if (path[item->nowildcard_len] == '*' && + no_wildcard(path + item->nowildcard_len + 1)) + item->flags |= PATHSPEC_ONESTAR; + } + } } qsort(pathspec->items, pathspec->nr, @@ -1385,3 +1483,11 @@ void free_pathspec(struct pathspec *pathspec) free(pathspec->items); pathspec->items = NULL; } + +int limit_pathspec_to_literal(void) +{ + static int flag = -1; + if (flag < 0) + flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + return flag; +} |