The pathspec mechanism is extended via the new
":(attr:eol=input)pattern/to/match" syntax to filter paths so that it
requires paths to not just match the given pattern but also have the
specified attrs attached for them to be chosen.

Signed-off-by: Stefan Beller <sbel...@google.com>
---
 attr.c     |  2 +-
 attr.h     |  2 ++
 dir.c      | 49 +++++++++++++++++++++++++++++++++
 pathspec.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 pathspec.h | 16 +++++++++++
 5 files changed, 157 insertions(+), 5 deletions(-)

diff --git a/attr.c b/attr.c
index 5178eb8..2d68a3c 100644
--- a/attr.c
+++ b/attr.c
@@ -59,7 +59,7 @@ static unsigned hash_name(const char *name, int namelen)
        return val;
 }
 
-static int invalid_attr_name(const char *name, int namelen)
+int invalid_attr_name(const char *name, int namelen)
 {
        /*
         * Attribute name cannot begin with '-' and must consist of
diff --git a/attr.h b/attr.h
index 7dc49f8..7fd8b90 100644
--- a/attr.h
+++ b/attr.h
@@ -45,6 +45,8 @@ extern void git_attr_check_append(struct git_attr_check *, 
const char *);
 extern void git_attr_check_clear(struct git_attr_check *);
 extern void git_attr_check_free(struct git_attr_check *);
 
+extern int invalid_attr_name(const char *name, int namelen);
+
 /*
  * Return the name of the attribute represented by the argument.  The
  * return value is a pointer to a null-delimited string that is part
diff --git a/dir.c b/dir.c
index 996653b..540ea1f 100644
--- a/dir.c
+++ b/dir.c
@@ -9,6 +9,7 @@
  */
 #include "cache.h"
 #include "dir.h"
+#include "attr.h"
 #include "refs.h"
 #include "wildmatch.h"
 #include "pathspec.h"
@@ -215,6 +216,51 @@ int within_depth(const char *name, int namelen,
        return 1;
 }
 
+static struct git_attr_check *check;
+static int match_attrs(const char *name, int namelen,
+                      const struct pathspec_item *item)
+{
+       char *path;
+       int i;
+
+       if (!check) {
+               check = git_attr_check_alloc();
+               for (i = 0; i < item->attr_nr; i++)
+                       git_attr_check_append(check, item->attrs[i].attr);
+       }
+
+       path = xmemdupz(name, namelen);
+       git_all_attrs(path, check);
+
+       for (i = 0; i < item->attr_nr; i++) {
+               int matched;
+               const char *value = check->check[i].value;
+
+               if (ATTR_TRUE(value)) {
+                       matched = (item->attrs[i].mode == MATCH_SET ||
+                                  item->attrs[i].mode == 
MATCH_NOT_UNSPECIFIED);
+               } else if (ATTR_FALSE(value)) {
+                       matched = (item->attrs[i].mode == MATCH_UNSET ||
+                                  item->attrs[i].mode == 
MATCH_NOT_UNSPECIFIED);
+               } else if (ATTR_UNSET(value)) {
+                       matched = (item->attrs[i].mode == MATCH_UNSPECIFIED);
+               } else {
+                       if (item->attrs[i].mode == MATCH_NOT_UNSPECIFIED) {
+                               matched = 1;
+                       } else {
+                               /* NEEDSWORK: better value matching */
+                               matched = !strcmp(item->attrs[i].value, value);
+                       }
+               }
+               if (!matched)
+                       return 0;
+       }
+
+       free(path);
+
+       return 1;
+}
+
 #define DO_MATCH_EXCLUDE   1
 #define DO_MATCH_DIRECTORY 2
 
@@ -270,6 +316,9 @@ static int match_pathspec_item(const struct pathspec_item 
*item, int prefix,
            strncmp(item->match, name - prefix, item->prefix))
                return 0;
 
+       if (item->attr_nr && !match_attrs(name, namelen, item))
+               return 0;
+
        /* If the match was just the prefix, we matched */
        if (!*match)
                return MATCHED_RECURSIVELY;
diff --git a/pathspec.c b/pathspec.c
index 4dff252..523ac8c 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "dir.h"
 #include "pathspec.h"
+#include "attr.h"
 
 /*
  * Finds which of the given pathspecs match items in the index.
@@ -88,12 +89,73 @@ static void prefix_short_magic(struct strbuf *sb, int 
prefixlen,
        strbuf_addf(sb, ",prefix:%d)", prefixlen);
 }
 
+/*
+ * Check if the given string is a good specification to search for attributes.
+ * Accepted strings:
+ * [+-!] ATTRIBUTE_NAME [= <space separated list of values> ]
+ *
+ * Examples:
+ * attr:+val to find value set to true
+ * attr:-val to find a value set to false
+ * attr:!val to find a value that is not set
+ *     (i.e. it is neither set as "val", "val=<empty>", nor unset as "-val")
+ * attr:val=value: to find value that have at least a and b set.
+ *
+ * This only checks one attr, multiple attrs need to be given as multiple
+ * strings.
+ */
+
+static void parse_attr_item(struct attr_item *attr, const char *value)
+{
+       size_t val_len;
+       const char *val = value;
+
+       if (!val || !strlen(val))
+               goto out;
+
+       if (val[0] == '+')
+               attr->mode = MATCH_SET;
+       else if (val[0] == '-')
+               attr->mode = MATCH_UNSET;
+       else if (val[0] == '!')
+               attr->mode = MATCH_UNSPECIFIED;
+       else
+               attr->mode = NOT_INIT;
+
+       if (attr->mode != NOT_INIT)
+               val++;
+
+       val_len = strcspn(val, "=,)");
+       if (val[val_len] == '=')
+               attr->mode = MATCH_VALUE;
+       else
+               attr->mode = MATCH_NOT_UNSPECIFIED;
+
+       if (invalid_attr_name(val, val_len))
+               goto out;
+
+       attr->attr = xmemdupz(val, val_len);
+
+       if (attr->mode == MATCH_VALUE) {
+               const char *after_equal = val + val_len + 1;
+               size_t after_equal_end = strcspn(after_equal, ",)");
+               attr->value = xmemdupz(after_equal, after_equal_end);
+       } else
+               attr->value = NULL;
+       return;
+out:
+       attr->mode = INVALID_ATTR;
+       warning(_("attr spec '%s': attrs must not start with '-' and "
+                 "be composed of [-A-Za-z0-9_.]."), value);
+}
+
 static void eat_long_magic(struct pathspec_item *item, const char *elt,
                unsigned *magic, int *pathspec_prefix,
                const char **copyfrom_, const char **long_magic_end)
 {
        int i;
        const char *copyfrom = *copyfrom_;
+       const char *body;
        /* longhand */
        const char *nextat;
        for (copyfrom = elt + 2;
@@ -108,15 +170,20 @@ static void eat_long_magic(struct pathspec_item *item, 
const char *elt,
                if (!len)
                        continue;
 
-               if (starts_with(copyfrom, "prefix:")) {
+               if (skip_prefix(copyfrom, "prefix:", &body)) {
                        char *endptr;
-                       *pathspec_prefix = strtol(copyfrom + 7,
-                                                 &endptr, 10);
+                       *pathspec_prefix = strtol(body, &endptr, 10);
                        if (endptr - copyfrom != len)
                                die(_("invalid parameter for pathspec magic 
'prefix'"));
                        continue;
                }
 
+               if (skip_prefix(copyfrom, "attr:", &body)) {
+                       ALLOC_GROW(item->attrs, item->attr_nr + 1, 
item->attr_alloc);
+                       parse_attr_item(&item->attrs[item->attr_nr++], body);
+                       continue;
+               }
+
                for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
                        if (strlen(pathspec_magic[i].name) == len &&
                            !strncmp(pathspec_magic[i].name, copyfrom, len)) {
@@ -425,7 +492,9 @@ void parse_pathspec(struct pathspec *pathspec,
        for (i = 0; i < n; i++) {
                unsigned short_magic;
                entry = argv[i];
-
+               item[i].attrs = NULL;
+               item[i].attr_nr = 0;
+               item[i].attr_alloc = 0;
                item[i].magic = prefix_pathspec(item + i, &short_magic,
                                                argv + i, flags,
                                                prefix, prefixlen, entry);
@@ -447,6 +516,13 @@ void parse_pathspec(struct pathspec *pathspec,
                if (item[i].nowildcard_len < item[i].len)
                        pathspec->has_wildcard = 1;
                pathspec->magic |= item[i].magic;
+
+               if (item[i].attr_nr) {
+                       int j;
+                       for (j = 0; j < item[i].attr_nr; j++)
+                               if (item[i].attrs[j].mode == INVALID_ATTR)
+                                       die(_("attribute spec in the wrong 
syntax are prohibited."));
+               }
        }
 
        if (nr_exclude == n)
@@ -502,6 +578,15 @@ void copy_pathspec(struct pathspec *dst, const struct 
pathspec *src)
 
 void free_pathspec(struct pathspec *pathspec)
 {
+       int i, j;
+       for (i = 0; i < pathspec->nr; i++) {
+               for (j = 0; j < pathspec->items[j].attr_nr; j++) {
+                       free(pathspec->items[i].attrs[j].attr);
+                       free(pathspec->items[i].attrs[j].value);
+               }
+               free(pathspec->items[i].attrs);
+       }
+
        free(pathspec->items);
        pathspec->items = NULL;
 }
diff --git a/pathspec.h b/pathspec.h
index 0c11262..89d73db 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -32,6 +32,21 @@ struct pathspec {
                int len, prefix;
                int nowildcard_len;
                int flags;
+               int attr_nr;
+               int attr_alloc;
+               struct attr_item {
+                       char *attr;
+                       char *value;
+                       enum attr_match_mode {
+                               NOT_INIT,
+                               MATCH_SET,
+                               MATCH_UNSET,
+                               MATCH_VALUE,
+                               MATCH_UNSPECIFIED,
+                               MATCH_NOT_UNSPECIFIED,
+                               INVALID_ATTR
+                       } mode;
+               } *attrs;
        } *items;
 };
 
@@ -98,5 +113,6 @@ extern char *find_pathspecs_matching_against_index(const 
struct pathspec *pathsp
 extern void add_pathspec_matches_against_index(const struct pathspec 
*pathspec, char *seen);
 extern const char *check_path_for_gitlink(const char *path);
 extern void die_if_path_beyond_symlink(const char *path, const char *prefix);
+extern int validate_label_name(const char *label);
 
 #endif /* PATHSPEC_H */
-- 
2.8.2.401.g9c0faef

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to