From: Stefan Beller <sbeller@google.com>
To: gitster@pobox.com, pclouds@gmail.com
Cc: git@vger.kernel.org, Stefan Beller <sbeller@google.com>
Subject: [PATCHv9 4/4] pathspec: allow querying for attributes
Date: Thu, 19 May 2016 16:23:23 -0700 [thread overview]
Message-ID: <20160519232323.12775-5-sbeller@google.com> (raw)
In-Reply-To: <20160519232323.12775-1-sbeller@google.com>
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 <sbeller@google.com>
---
Documentation/glossary-content.txt | 20 +++++
dir.c | 35 ++++++++
pathspec.c | 101 ++++++++++++++++++++++-
pathspec.h | 16 ++++
t/t6134-pathspec-with-labels.sh | 163 +++++++++++++++++++++++++++++++++++++
5 files changed, 331 insertions(+), 4 deletions(-)
create mode 100755 t/t6134-pathspec-with-labels.sh
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index cafc284..e06520b 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -384,6 +384,26 @@ full pathname may have special meaning:
+
Glob magic is incompatible with literal magic.
+attr;;
+After `attr:` comes a space separated list of "attribute
+requirements", all of which must be met in order for the
+path to be considered a match; this is in addition to the
+usual non-magic pathspec pattern matching.
+
+Each of the attribute requirements for the path takes one of
+these forms:
+
+- "`ATTR`" requires that the attribute `ATTR` must be set.
+
+- "`-ATTR`" requires that the attribute `ATTR` must be unset.
+
+- "`ATTR=VALUE`" requires that the attribute `ATTR` must be
+ set to the string `VALUE`.
+
+- "`!ATTR`" requires that the attribute `ATTR` must be
+ unspecified.
++
+
exclude;;
After a path matches any non-exclude pathspec, it will be run
through all exclude pathspec (magic signature: `!`). If it
diff --git a/dir.c b/dir.c
index 996653b..fc071af 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,37 @@ int within_depth(const char *name, int namelen,
return 1;
}
+static int match_attrs(const char *name, int namelen,
+ const struct pathspec_item *item)
+{
+ int i;
+
+ git_check_attr_counted(name, namelen, item->attr_check);
+ for (i = 0; i < item->attr_match_nr; i++) {
+ const char *value;
+ int matched;
+ enum attr_match_mode match_mode;
+
+ value = item->attr_check->check[i].value;
+ match_mode = item->attr_match[i].match_mode;
+
+ if (ATTR_TRUE(value))
+ matched = (match_mode == MATCH_SET);
+ else if (ATTR_FALSE(value))
+ matched = (match_mode == MATCH_UNSET);
+ else if (ATTR_UNSET(value))
+ matched = (match_mode == MATCH_UNSPECIFIED);
+ else
+ matched = (match_mode == MATCH_VALUE &&
+ !strcmp(item->attr_match[i].value, value));
+
+ if (!matched)
+ return 0;
+ }
+
+ return 1;
+}
+
#define DO_MATCH_EXCLUDE 1
#define DO_MATCH_DIRECTORY 2
@@ -270,6 +302,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_match_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..693a5e7 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,78 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
strbuf_addf(sb, ",prefix:%d)", prefixlen);
}
+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 (!value || !strlen(value))
+ die(_("attr spec must not be empty"));
+
+ string_list_split(&list, value, ' ', -1);
+ string_list_remove_empty_items(&list, 0);
+
+ if (!item->attr_check)
+ item->attr_check = git_attr_check_alloc();
+ else
+ die(_("Only one 'attr:' specification is allowed."));
+
+ ALLOC_GROW(item->attr_match, item->attr_match_nr + list.nr, item->attr_match_alloc);
+
+ for_each_string_list_item(si, &list) {
+ size_t attr_len;
+
+ int j = item->attr_match_nr++;
+ const char *attr = si->string;
+ struct attr_match *am = &item->attr_match[j];
+
+ attr_len = strcspn(attr, "=");
+ switch (*attr) {
+ case '!':
+ am->match_mode = MATCH_UNSPECIFIED;
+ attr++;
+ attr_len--;
+ break;
+ case '-':
+ am->match_mode = MATCH_UNSET;
+ attr++;
+ attr_len--;
+ break;
+ default:
+ if (attr[attr_len] != '=')
+ am->match_mode = MATCH_SET;
+ else {
+ am->match_mode = MATCH_VALUE;
+ am->value = xstrdup(&attr[attr_len + 1]);
+ if (strchr(am->value, '\\'))
+ die(_("attr spec values must not contain backslashes"));
+ }
+ break;
+ }
+
+ am->attr = git_attr_counted(attr, attr_len);
+ if (!am->attr) {
+ struct strbuf sb = STRBUF_INIT;
+ am->match_mode = INVALID_ATTR;
+ invalid_attr_name_message(&sb, attr, attr_len);
+ die(_("invalid attribute in '%s': '%s'"), value, sb.buf);
+ }
+
+ git_attr_check_append(item->attr_check, am->attr);
+ }
+
+ string_list_clear(&list, 0);
+ return;
+}
+
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 +175,21 @@ 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)) {
+ char *attr_body = xmemdupz(body, len - strlen("attr:"));
+ parse_pathspec_attr_match(item, attr_body);
+ 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, copyfrom, len)) {
@@ -425,7 +498,10 @@ void parse_pathspec(struct pathspec *pathspec,
for (i = 0; i < n; i++) {
unsigned short_magic;
entry = argv[i];
-
+ item[i].attr_check = NULL;
+ item[i].attr_match = NULL;
+ item[i].attr_match_nr = 0;
+ item[i].attr_match_alloc = 0;
item[i].magic = prefix_pathspec(item + i, &short_magic,
argv + i, flags,
prefix, prefixlen, entry);
@@ -447,6 +523,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_match_nr) {
+ int j;
+ for (j = 0; j < item[i].attr_match_nr; j++)
+ if (item[i].attr_match[j].match_mode == INVALID_ATTR)
+ die(_("attribute spec in the wrong syntax are prohibited."));
+ }
}
if (nr_exclude == n)
@@ -502,6 +585,16 @@ 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++) {
+ if (!pathspec->items[i].attr_match_nr)
+ continue;
+ for (j = 0; j < pathspec->items[j].attr_match_nr; j++)
+ free(pathspec->items[i].attr_match[j].value);
+ free(pathspec->items[i].attr_match);
+ git_attr_check_free(pathspec->items[i].attr_check);
+ }
+
free(pathspec->items);
pathspec->items = NULL;
}
diff --git a/pathspec.h b/pathspec.h
index 0c11262..5308137 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -32,6 +32,22 @@ struct pathspec {
int len, prefix;
int nowildcard_len;
int flags;
+ int attr_match_nr;
+ int attr_match_alloc;
+ struct attr_match {
+ struct git_attr *attr;
+ char *value;
+ enum attr_match_mode {
+ MATCH_SET,
+ MATCH_UNSET,
+ MATCH_VALUE,
+ MATCH_UNSPECIFIED,
+ MATCH_NOT_UNSPECIFIED,
+ MATCH_SET_OR_VALUE,
+ INVALID_ATTR
+ } match_mode;
+ } *attr_match;
+ struct git_attr_check *attr_check;
} *items;
};
diff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh
new file mode 100755
index 0000000..5da1a63
--- /dev/null
+++ b/t/t6134-pathspec-with-labels.sh
@@ -0,0 +1,163 @@
+#!/bin/sh
+
+test_description='test labels in pathspecs'
+. ./test-lib.sh
+
+test_expect_success 'setup a tree' '
+ cat <<-EOF >expect &&
+ fileA
+ fileAB
+ fileAC
+ fileB
+ fileBC
+ fileC
+ fileNoLabel
+ fileSetLabel
+ fileUnsetLabel
+ fileValue
+ fileWrongLabel
+ sub/fileA
+ sub/fileAB
+ sub/fileAC
+ sub/fileB
+ sub/fileBC
+ sub/fileC
+ sub/fileNoLabel
+ sub/fileSetLabel
+ sub/fileUnsetLabel
+ sub/fileValue
+ sub/fileWrongLabel
+ EOF
+ mkdir sub &&
+ while read path
+ do
+ : >$path &&
+ git add $path || return 1
+ done <expect &&
+ git commit -m "initial commit" &&
+ git ls-files >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'pathspec with no attr' '
+ test_must_fail git ls-files ":(attr:)" 2>actual &&
+ test_i18ngrep fatal actual
+'
+
+test_expect_success 'pathspec with labels and non existent .gitattributes' '
+ git ls-files ":(attr:label)" >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'setup .gitattributes' '
+ cat <<-EOF >.gitattributes &&
+ fileA labelA
+ fileB labelB
+ fileC labelC
+ fileAB labelA labelB
+ fileAC labelA labelC
+ fileBC labelB labelC
+ fileUnsetLabel -label
+ fileSetLabel label
+ fileValue label=foo
+ fileWrongLabel label☺
+ EOF
+ git add .gitattributes &&
+ git commit -m "add attributes"
+'
+
+test_expect_success 'check specific set attr' '
+ cat <<-EOF >expect &&
+ fileSetLabel
+ sub/fileSetLabel
+ EOF
+ git ls-files ":(attr:label)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check specific unset attr' '
+ cat <<-EOF >expect &&
+ fileUnsetLabel
+ sub/fileUnsetLabel
+ EOF
+ git ls-files ":(attr:-label)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check specific value attr' '
+ cat <<-EOF >expect &&
+ fileValue
+ sub/fileValue
+ EOF
+ git ls-files ":(attr:label=foo)" >actual &&
+ test_cmp expect actual &&
+ git ls-files ":(attr:label=bar)" >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'check unspecified attr' '
+ cat <<-EOF >expect &&
+ .gitattributes
+ fileA
+ fileAB
+ fileAC
+ fileB
+ fileBC
+ fileC
+ fileNoLabel
+ fileWrongLabel
+ sub/fileA
+ sub/fileAB
+ sub/fileAC
+ sub/fileB
+ sub/fileBC
+ sub/fileC
+ sub/fileNoLabel
+ sub/fileWrongLabel
+ EOF
+ git ls-files ":(attr:!label)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check multiple unspecified attr' '
+ cat <<-EOF >expect &&
+ .gitattributes
+ fileC
+ fileNoLabel
+ fileWrongLabel
+ sub/fileC
+ sub/fileNoLabel
+ sub/fileWrongLabel
+ EOF
+ git ls-files ":(attr:!labelB !labelA !label)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check label with more labels but excluded path' '
+ cat <<-EOF >expect &&
+ fileAB
+ fileB
+ fileBC
+ EOF
+ git ls-files ":(attr:labelB)" ":(exclude)sub/" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check label excluding other labels' '
+ cat <<-EOF >expect &&
+ fileAB
+ fileB
+ fileBC
+ sub/fileAB
+ sub/fileB
+ EOF
+ git ls-files ":(attr:labelB)" ":(exclude,attr:labelC)sub/" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'abort on giving invalid label on the command line' '
+ test_must_fail git ls-files . ":(attr:☺)" 2>actual &&
+ test_i18ngrep "fatal" actual
+'
+
+test_done
--
2.8.2.123.gb4ad9b6.dirty
next prev parent reply other threads:[~2016-05-19 23:23 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-05-19 23:23 [PATCHv9 0/4] pathspec magic extension to search for attributes Stefan Beller
2016-05-19 23:23 ` [PATCHv9 1/4] Documentation: fix a typo Stefan Beller
2016-05-19 23:23 ` [PATCHv9 2/4] pathspec: move long magic parsing out of prefix_pathspec Stefan Beller
2016-05-19 23:23 ` [PATCHv9 3/4] pathspec: move prefix check out of the inner loop Stefan Beller
2016-05-19 23:23 ` Stefan Beller [this message]
2016-05-19 23:32 ` [PATCHv9 4/4] pathspec: allow querying for attributes Junio C Hamano
2016-05-20 18:15 ` Junio C Hamano
2016-05-20 18:21 ` Stefan Beller
2016-05-20 18:31 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20160519232323.12775-5-sbeller@google.com \
--to=sbeller@google.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=pclouds@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).