diff options
Diffstat (limited to 'pathspec.c')
-rw-r--r-- | pathspec.c | 272 |
1 files changed, 180 insertions, 92 deletions
diff --git a/pathspec.c b/pathspec.c index 7ababb3159..e2a23ebc96 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,6 +1,9 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "dir.h" #include "pathspec.h" +#include "attr.h" /* * Finds which of the given pathspecs match items in the index. @@ -16,6 +19,7 @@ * to use find_pathspecs_matching_against_index() instead. */ void add_pathspec_matches_against_index(const struct pathspec *pathspec, + const struct index_state *istate, char *seen) { int num_unmatched = 0, i; @@ -31,8 +35,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, num_unmatched++; if (!num_unmatched) return; - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; ce_path_match(ce, pathspec, seen); } } @@ -45,10 +49,11 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, * nature of the "closest" (i.e. most specific) matches which each of the * given pathspecs achieves against all items in the index. */ -char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) +char *find_pathspecs_matching_against_index(const struct pathspec *pathspec, + const struct index_state *istate) { char *seen = xcalloc(pathspec->nr, 1); - add_pathspec_matches_against_index(pathspec, seen); + add_pathspec_matches_against_index(pathspec, istate, seen); return seen; } @@ -72,6 +77,7 @@ static struct pathspec_magic { { PATHSPEC_GLOB, '\0', "glob" }, { PATHSPEC_ICASE, '\0', "icase" }, { PATHSPEC_EXCLUDE, '!', "exclude" }, + { PATHSPEC_ATTR, '\0', "attr" }, }; static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) @@ -87,6 +93,116 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) strbuf_addf(sb, ",prefix:%d)", prefixlen); } +static size_t strcspn_escaped(const char *s, const char *stop) +{ + const char *i; + + for (i = s; *i; i++) { + /* skip the escaped character */ + if (i[0] == '\\' && i[1]) { + i++; + continue; + } + + if (strchr(stop, *i)) + break; + } + return i - s; +} + +static inline int invalid_value_char(const char ch) +{ + if (isalnum(ch) || strchr(",-_", ch)) + return 0; + return -1; +} + +static char *attr_value_unescape(const char *value) +{ + const char *src; + char *dst, *ret; + + ret = xmallocz(strlen(value)); + for (src = value, dst = ret; *src; src++, dst++) { + if (*src == '\\') { + if (!src[1]) + die(_("Escape character '\\' not allowed as " + "last character in attr value")); + src++; + } + if (invalid_value_char(*src)) + die("cannot use '%c' for value matching", *src); + *dst = *src; + } + *dst = '\0'; + return ret; +} + +static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value) +{ + struct string_list_item *si; + struct string_list list = STRING_LIST_INIT_DUP; + + if (item->attr_check || item->attr_match) + die(_("Only one 'attr:' specification is allowed.")); + + if (!value || !*value) + die(_("attr spec must not be empty")); + + string_list_split(&list, value, ' ', -1); + string_list_remove_empty_items(&list, 0); + + item->attr_check = attr_check_alloc(); + item->attr_match = xcalloc(list.nr, sizeof(struct attr_match)); + + for_each_string_list_item(si, &list) { + size_t attr_len; + char *attr_name; + const struct git_attr *a; + + int j = item->attr_match_nr++; + const char *attr = si->string; + struct attr_match *am = &item->attr_match[j]; + + switch (*attr) { + case '!': + am->match_mode = MATCH_UNSPECIFIED; + attr++; + attr_len = strlen(attr); + break; + case '-': + am->match_mode = MATCH_UNSET; + attr++; + attr_len = strlen(attr); + break; + default: + attr_len = strcspn(attr, "="); + if (attr[attr_len] != '=') + am->match_mode = MATCH_SET; + else { + const char *v = &attr[attr_len + 1]; + am->match_mode = MATCH_VALUE; + am->value = attr_value_unescape(v); + } + break; + } + + attr_name = xmemdupz(attr, attr_len); + a = git_attr(attr_name); + if (!a) + die(_("invalid attribute name %s"), attr_name); + + attr_check_append(item->attr_check, a); + + free(attr_name); + } + + if (item->attr_check->nr != item->attr_match_nr) + die("BUG: should have same number of entries"); + + string_list_clear(&list, 0); +} + static inline int get_literal_global(void) { static int literal = -1; @@ -164,13 +280,14 @@ static int get_global_magic(int element_magic) * returns the position in 'elem' after all magic has been parsed */ static const char *parse_long_magic(unsigned *magic, int *prefix_len, + struct pathspec_item *item, const char *elem) { const char *pos; const char *nextat; for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) { - size_t len = strcspn(pos, ",)"); + size_t len = strcspn_escaped(pos, ",)"); int i; if (pos[len] == ',') @@ -189,6 +306,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len, continue; } + if (starts_with(pos, "attr:")) { + char *attr_body = xmemdupz(pos + 5, len - 5); + parse_pathspec_attr_match(item, attr_body); + *magic |= PATHSPEC_ATTR; + free(attr_body); + continue; + } + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) == len && !strncmp(pathspec_magic[i].name, pos, len)) { @@ -224,6 +349,12 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) char ch = *pos; int i; + /* Special case alias for '!' */ + if (ch == '^') { + *magic |= PATHSPEC_EXCLUDE; + continue; + } + if (!is_pathspec_magic(ch)) break; @@ -246,77 +377,19 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) } static const char *parse_element_magic(unsigned *magic, int *prefix_len, + struct pathspec_item *item, const char *elem) { if (elem[0] != ':' || get_literal_global()) return elem; /* nothing to do */ else if (elem[1] == '(') /* longhand */ - return parse_long_magic(magic, prefix_len, elem); + return parse_long_magic(magic, prefix_len, item, elem); else /* shorthand */ return parse_short_magic(magic, elem); } -static void strip_submodule_slash_cheap(struct pathspec_item *item) -{ - if (item->len >= 1 && item->match[item->len - 1] == '/') { - int i = cache_name_pos(item->match, item->len - 1); - - if (i >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) { - item->len--; - item->match[item->len] = '\0'; - } - } -} - -static void strip_submodule_slash_expensive(struct pathspec_item *item) -{ - int i; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len <= ce_len || item->match[ce_len] != '/' || - memcmp(ce->name, item->match, ce_len)) - continue; - - if (item->len == ce_len + 1) { - /* strip trailing slash */ - item->len--; - item->match[item->len] = '\0'; - } else { - die(_("Pathspec '%s' is in submodule '%.*s'"), - item->original, ce_len, ce->name); - } - } -} - -static void die_inside_submodule_path(struct pathspec_item *item) -{ - int i; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len < ce_len || - !(item->match[ce_len] == '/' || item->match[ce_len] == '\0') || - memcmp(ce->name, item->match, ce_len)) - continue; - - die(_("Pathspec '%s' is in submodule '%.*s'"), - item->original, ce_len, ce->name); - } -} - /* * Perform the initialization of a pathspec_item based on a pathspec element. */ @@ -329,12 +402,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, char *match; int pathspec_prefix = -1; + item->attr_check = NULL; + item->attr_match = NULL; + item->attr_match_nr = 0; + /* PATHSPEC_LITERAL_PATH ignores magic */ if (flags & PATHSPEC_LITERAL_PATH) { magic = PATHSPEC_LITERAL; } else { copyfrom = parse_element_magic(&element_magic, &pathspec_prefix, + item, elt); magic |= element_magic; magic |= get_global_magic(element_magic); @@ -372,7 +450,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, * original. Useful for passing to another command. */ if ((flags & PATHSPEC_PREFIX_ORIGIN) && - prefixlen && !get_literal_global()) { + !get_literal_global()) { struct strbuf sb = STRBUF_INIT; /* Preserve the actual prefix length of each pattern */ @@ -384,12 +462,6 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, item->original = xstrdup(elt); } - if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) - strip_submodule_slash_cheap(item); - - if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) - strip_submodule_slash_expensive(item); - if (magic & PATHSPEC_LITERAL) { item->nowildcard_len = item->len; } else { @@ -414,15 +486,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, /* sanity checks, pathspec matchers assume these are sane */ if (item->nowildcard_len > item->len || item->prefix > item->len) { - /* - * This case can be triggered by the user pointing us to a - * pathspec inside a submodule, which is an input error. - * Detect that here and complain, but fallback in the - * non-submodule case to a BUG, as we have no idea what - * would trigger that. - */ - die_inside_submodule_path(item); - die ("BUG: item->nowildcard_len > item->len || item->prefix > item->len)"); + die ("BUG: error initializing pathspec_item"); } } @@ -516,7 +580,7 @@ void parse_pathspec(struct pathspec *pathspec, } pathspec->nr = n; - ALLOC_ARRAY(pathspec->items, n); + ALLOC_ARRAY(pathspec->items, n + 1); item = pathspec->items; prefixlen = prefix ? strlen(prefix) : 0; @@ -540,10 +604,15 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->magic |= item[i].magic; } - if (nr_exclude == n) - die(_("There is nothing to exclude from by :(exclude) patterns.\n" - "Perhaps you forgot to add either ':/' or '.' ?")); - + /* + * If everything is an exclude pattern, add one positive pattern + * that matches everything. We allocated an extra one for this. + */ + if (nr_exclude == n) { + int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen; + init_pathspec_item(item + n, 0, prefix, plen, ""); + pathspec->nr++; + } if (pathspec->magic & PATHSPEC_MAXDEPTH) { if (flags & PATHSPEC_KEEP_ORDER) @@ -554,27 +623,46 @@ void parse_pathspec(struct pathspec *pathspec, void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { - int i; + int i, j; *dst = *src; ALLOC_ARRAY(dst->items, dst->nr); COPY_ARRAY(dst->items, src->items, dst->nr); for (i = 0; i < dst->nr; i++) { - dst->items[i].match = xstrdup(src->items[i].match); - dst->items[i].original = xstrdup(src->items[i].original); + struct pathspec_item *d = &dst->items[i]; + struct pathspec_item *s = &src->items[i]; + + d->match = xstrdup(s->match); + d->original = xstrdup(s->original); + + ALLOC_ARRAY(d->attr_match, d->attr_match_nr); + COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr); + for (j = 0; j < d->attr_match_nr; j++) { + const char *value = s->attr_match[j].value; + d->attr_match[j].value = xstrdup_or_null(value); + } + + d->attr_check = attr_check_dup(s->attr_check); } } void clear_pathspec(struct pathspec *pathspec) { - int i; + int i, j; for (i = 0; i < pathspec->nr; i++) { free(pathspec->items[i].match); free(pathspec->items[i].original); + + for (j = 0; j < pathspec->items[i].attr_match_nr; j++) + free(pathspec->items[i].attr_match[j].value); + free(pathspec->items[i].attr_match); + + if (pathspec->items[i].attr_check) + attr_check_free(pathspec->items[i].attr_check); } - free(pathspec->items); - pathspec->items = NULL; + + FREE_AND_NULL(pathspec->items); pathspec->nr = 0; } |