diff options
Diffstat (limited to 'lib/exclude.c')
-rw-r--r-- | lib/exclude.c | 163 |
1 files changed, 75 insertions, 88 deletions
diff --git a/lib/exclude.c b/lib/exclude.c index df49714..a7dd9b3 100644 --- a/lib/exclude.c +++ b/lib/exclude.c @@ -1,6 +1,6 @@ /* exclude.c -- exclude file names - Copyright (C) 1992-1994, 1997, 1999-2007, 2009-2011 Free Software + Copyright (C) 1992-1994, 1997, 1999-2007, 2009-2013 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify @@ -104,53 +104,46 @@ struct exclude_segment } v; }; -/* The exclude structure keeps a singly-linked list of exclude segments */ +/* The exclude structure keeps a singly-linked list of exclude segments, + maintained in reverse order. */ struct exclude { - struct exclude_segment *head, *tail; + struct exclude_segment *head; }; -/* Return true if str has wildcard characters */ +/* Return true if STR has or may have wildcards, when matched with OPTIONS. + Return false if STR definitely does not have wildcards. */ bool fnmatch_pattern_has_wildcards (const char *str, int options) { - const char *cset = "\\?*[]"; - if (options & FNM_NOESCAPE) - cset++; - while (*str) + while (1) { - size_t n = strcspn (str, cset); - if (str[n] == 0) - break; - else if (str[n] == '\\') + switch (*str++) { - str += n + 1; - if (*str) - str++; + case '\\': + str += ! (options & FNM_NOESCAPE) && *str; + break; + + case '+': case '@': case '!': + if (options & FNM_EXTMATCH && *str == '(') + return true; + break; + + case '?': case '*': case '[': + return true; + + case '\0': + return false; } - else - return true; } - return false; } static void unescape_pattern (char *str) { - int inset = 0; - char *q = str; + char const *q = str; do - { - if (inset) - { - if (*q == ']') - inset = 0; - } - else if (*q == '[') - inset = 1; - else if (*q == '\\') - q++; - } + q += *q == '\\' && q[1]; while ((*str++ = *q++)); } @@ -219,8 +212,8 @@ string_free (void *data) } /* Create new exclude segment of given TYPE and OPTIONS, and attach it - to the tail of list in EX */ -static struct exclude_segment * + to the head of EX. */ +static void new_exclude_segment (struct exclude *ex, enum exclude_type type, int options) { struct exclude_segment *sp = xzalloc (sizeof (struct exclude_segment)); @@ -242,12 +235,8 @@ new_exclude_segment (struct exclude *ex, enum exclude_type type, int options) string_free); break; } - if (ex->tail) - ex->tail->next = sp; - else - ex->head = sp; - ex->tail = sp; - return sp; + sp->next = ex->head; + ex->head = sp; } /* Free a single exclude segment */ @@ -347,36 +336,33 @@ exclude_fnmatch (char const *pattern, char const *f, int options) return matched; } -/* Return true if the exclude_pattern segment SEG excludes F. */ +/* Return true if the exclude_pattern segment SEG matches F. */ static bool -excluded_file_pattern_p (struct exclude_segment const *seg, char const *f) +file_pattern_matches (struct exclude_segment const *seg, char const *f) { size_t exclude_count = seg->v.pat.exclude_count; struct patopts const *exclude = seg->v.pat.exclude; size_t i; - bool excluded = !! (exclude[0].options & EXCLUDE_INCLUDE); - /* Scan through the options, until they change excluded */ for (i = 0; i < exclude_count; i++) { char const *pattern = exclude[i].pattern; int options = exclude[i].options; if (exclude_fnmatch (pattern, f, options)) - return !excluded; + return true; } - return excluded; + return false; } -/* Return true if the exclude_hash segment SEG excludes F. +/* Return true if the exclude_hash segment SEG matches F. BUFFER is an auxiliary storage of the same length as F (with nul terminator included) */ static bool -excluded_file_name_p (struct exclude_segment const *seg, char const *f, - char *buffer) +file_name_matches (struct exclude_segment const *seg, char const *f, + char *buffer) { int options = seg->options; - bool excluded = !! (options & EXCLUDE_INCLUDE); Hash_table *table = seg->v.table; do @@ -387,7 +373,7 @@ excluded_file_name_p (struct exclude_segment const *seg, char const *f, while (1) { if (hash_lookup (table, buffer)) - return !excluded; + return true; if (options & FNM_LEADING_DIR) { char *p = strrchr (buffer, '/'); @@ -410,7 +396,8 @@ excluded_file_name_p (struct exclude_segment const *seg, char const *f, break; } while (f); - return excluded; + + return false; } /* Return true if EX excludes F. */ @@ -419,44 +406,46 @@ bool excluded_file_name (struct exclude const *ex, char const *f) { struct exclude_segment *seg; - bool excluded; + bool invert = false; char *filename = NULL; /* If no patterns are given, the default is to include. */ if (!ex->head) return false; - /* Otherwise, the default is the opposite of the first option. */ - excluded = !! (ex->head->options & EXCLUDE_INCLUDE); - /* Scan through the segments, seeing whether they change status from - excluded to included or vice versa. */ - for (seg = ex->head; seg; seg = seg->next) + /* Scan through the segments, reporting the status of the first match. + The segments are in reverse order, so this reports the status of + the last match in the original option list. */ + for (seg = ex->head; ; seg = seg->next) { - bool rc; - - switch (seg->type) + if (seg->type == exclude_hash) { - case exclude_pattern: - rc = excluded_file_pattern_p (seg, f); - break; - - case exclude_hash: if (!filename) filename = xmalloc (strlen (f) + 1); - rc = excluded_file_name_p (seg, f, filename); - break; - - default: - abort (); + if (file_name_matches (seg, f, filename)) + break; } - if (rc != excluded) + else + { + if (file_pattern_matches (seg, f)) + break; + } + + if (! seg->next) { - excluded = rc; + /* If patterns are given but none match, the default is the + opposite of the last segment (i.e., the first in the + original option list). For example, in the command + 'grep -r --exclude="a*" --include="*b" pat dir', the + first option is --exclude so any file name matching + neither a* nor *b is included. */ + invert = true; break; } } + free (filename); - return excluded; + return invert ^ ! (seg->options & EXCLUDE_INCLUDE); } /* Append to EX the exclusion PATTERN with OPTIONS. */ @@ -472,12 +461,11 @@ add_exclude (struct exclude *ex, char const *pattern, int options) struct exclude_pattern *pat; struct patopts *patopts; - if (ex->tail && ex->tail->type == exclude_pattern - && ((ex->tail->options & EXCLUDE_INCLUDE) == - (options & EXCLUDE_INCLUDE))) - seg = ex->tail; - else - seg = new_exclude_segment (ex, exclude_pattern, options); + if (! (ex->head && ex->head->type == exclude_pattern + && ((ex->head->options & EXCLUDE_INCLUDE) + == (options & EXCLUDE_INCLUDE)))) + new_exclude_segment (ex, exclude_pattern, options); + seg = ex->head; pat = &seg->v.pat; if (pat->exclude_count == pat->exclude_alloc) @@ -490,17 +478,16 @@ add_exclude (struct exclude *ex, char const *pattern, int options) else { char *str, *p; -#define EXCLUDE_HASH_FLAGS (EXCLUDE_INCLUDE|EXCLUDE_ANCHORED|\ - FNM_LEADING_DIR|FNM_CASEFOLD) - if (ex->tail && ex->tail->type == exclude_hash - && ((ex->tail->options & EXCLUDE_HASH_FLAGS) == - (options & EXCLUDE_HASH_FLAGS))) - seg = ex->tail; - else - seg = new_exclude_segment (ex, exclude_hash, options); + int exclude_hash_flags = (EXCLUDE_INCLUDE | EXCLUDE_ANCHORED + | FNM_LEADING_DIR | FNM_CASEFOLD); + if (! (ex->head && ex->head->type == exclude_hash + && ((ex->head->options & exclude_hash_flags) + == (options & exclude_hash_flags)))) + new_exclude_segment (ex, exclude_hash, options); + seg = ex->head; str = xstrdup (pattern); - if (options & EXCLUDE_WILDCARDS) + if ((options & (EXCLUDE_WILDCARDS | FNM_NOESCAPE)) == EXCLUDE_WILDCARDS) unescape_pattern (str); p = hash_insert (seg->v.table, str); if (p != str) |