[PATCHv7 0/5] pathspec magic extension to search for attributes

7 messages, 1 author, 2016-06-16 · open the first message on its own page

[PATCHv7 0/5] pathspec magic extension to search for attributes

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:27

This goes on top of origin/jc/attr, (8195c5cde01, SQUASH???, 2016-05-17 14:08:50)

Patches 1 and 2 are small fixes, which could go independently as well.
(1 is new compared to v6)

Patches 3 and 4 are refactoring pathspec.c a little.

The last patch is the most exciting patch as it adds the new feature.
Quoting from the documentation update:

        attr;;
                Additionally to matching the pathspec, the path must have the
                attribute as specified. The syntax for specifying the required
                attributes is "`attr: [mode] <attribute name> [=value]`"
        +
        Attributes can have 4 states (Set, Unset, Set to a value, unspecified) and
        you can query each attribute for certain states. The "`[mode]`" is a special
        character to indicate which attribute states are looked for. The following
        modes are available:
        
        - "`+`" the attribute must be set
        - "`-`" the attribute must be unset
        - "`~`" the attribute must be unspecified
        - "`?`" the attribute must not be unspecified, i.e. set, unset or value matches
        - an empty "`[mode]`" matches if the attribute is set or has a value
        - an empty "`[mode]`" combined with "`[=value]`" matches if the attribute has
          the given value.
        +

While I followed Junios suggestion of the data layout in pathspec.h,
I am not quite happy with it yet as I think it will not be easy in the future
to enhance search for specific attribute values. Quoting from the tests:

        test_expect_success 'check label with 2 labels' '
                cat <<EOF >expect &&
        fileAB     # has both labels
        sub/fileAB # has both labels
        EOF
                git ls-files ":(attr:labelA labelB)" >actual &&
                test_cmp expect actual &&
                git ls-files ":(attr:labelA,attr:labelB)" >actual &&
                test_cmp expect actual
        '

You can see that two notations are possible to narrow the search down when
asking for 2 attributes to be set. Either you can do 2 "attr:" magic strings
or you can have one "attr:" and then a space separated list of attributes.

The problem arises once you'd want to have a better search for values, as
currently only ":(attr:label=value) is supported with "value" being the
exact match, e.g. a path having more than just value would not match as it is
not exact. E.g. in git.git we have

        $ cat .gitattributes 
        * whitespace=!indent,trail,space
        *.[ch] whitespace=indent,trail,space
        *.sh whitespace=indent,trail,space
        $ git ls-files ":(attr:whitespace=indent)"
        $ # no match!

So instead of matching exactly we'd want to just look for the value being
one part of the comma separated list as setup in .gitattributes.
However searching for more than one value will be complicated, as we'd then
need to escape commas. My vision was to use white spaces instead, but they
are now taken to separate different attributes.

So in the future I would imagine to have escaping like 

        git ls-files ":(attr:label=value\,value2 labelA=value3,exclude)
        
or yet another separator like

        git ls-files ":(attr:label=value1;value2 labelA=value3,exclude)

Thanks,
Stefan

        
Stefan Beller (5):
  string list: improve comment
  Documentation: fix a typo
  pathspec: move long magic parsing out of prefix_pathspec
  pathspec: move prefix check out of the inner loop
  pathspec: allow querying for attributes

 Documentation/gitattributes.txt    |   2 +-
 Documentation/glossary-content.txt |  19 ++++
 attr.c                             |   2 +-
 attr.h                             |   2 +
 dir.c                              |  46 ++++++++++
 pathspec.c                         | 182 +++++++++++++++++++++++++++++--------
 pathspec.h                         |  16 ++++
 string-list.h                      |   2 +-
 t/t6134-pathspec-with-labels.sh    | 177 ++++++++++++++++++++++++++++++++++++
 9 files changed, 407 insertions(+), 41 deletions(-)
 create mode 100755 t/t6134-pathspec-with-labels.sh

-- 
2.8.2.121.ga97fb08

[PATCHv7 2/5] Documentation: fix a typo

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:26

Signed-off-by: Stefan Beller <redacted>
---
 Documentation/gitattributes.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index e3b1de8..af2c682 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -86,7 +86,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.
 Attributes for all users on a system should be placed in the
 `$(prefix)/etc/gitattributes` file.
 
-Sometimes you would need to override an setting of an attribute
+Sometimes you would need to override a setting of an attribute
 for a path to `Unspecified` state.  This can be done by listing
 the name of the attribute prefixed with an exclamation point `!`.
 
-- 
2.8.2.121.ga97fb08

[PATCHv7 1/5] string list: improve comment

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:26

Signed-off-by: Stefan Beller <redacted>
---
 string-list.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/string-list.h b/string-list.h
index d3809a1..465a1f0 100644
--- a/string-list.h
+++ b/string-list.h
@@ -106,7 +106,7 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
  * list->strdup_strings must be set, as new memory needs to be
  * allocated to hold the substrings.  If maxsplit is non-negative,
  * then split at most maxsplit times.  Return the number of substrings
- * appended to list.
+ * appended to list. The list may be non-empty already.
  *
  * Examples:
  *   string_list_split(l, "foo:bar:baz", ':', -1) -> ["foo", "bar", "baz"]
-- 
2.8.2.121.ga97fb08

[PATCHv7 5/5] pathspec: allow querying for attributes

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:27

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 <redacted>
---
 Documentation/glossary-content.txt |  19 ++++
 attr.c                             |   2 +-
 attr.h                             |   2 +
 dir.c                              |  46 ++++++++++
 pathspec.c                         | 103 ++++++++++++++++++++-
 pathspec.h                         |  16 ++++
 t/t6134-pathspec-with-labels.sh    | 177 +++++++++++++++++++++++++++++++++++++
 7 files changed, 360 insertions(+), 5 deletions(-)
 create mode 100755 t/t6134-pathspec-with-labels.sh
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index cafc284..866e8d8 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -384,6 +384,25 @@ full pathname may have special meaning:
 +
 Glob magic is incompatible with literal magic.
 
+attr;;
+	Additionally to matching the pathspec, the path must have the
+	attribute as specified. The syntax for specifying the required
+	attributes is "`attr: [mode] <attribute name> [=value]`"
++
+Attributes can have 4 states (Set, Unset, Set to a value, unspecified) and
+you can query each attribute for certain states. The "`[mode]`" is a special
+character to indicate which attribute states are looked for. The following
+modes are available:
+
+ - "`+`" the attribute must be set
+ - "`-`" the attribute must be unset
+ - "`~`" the attribute must be unspecified
+ - "`?`" the attribute must not be unspecified, i.e. set, unset or value matches
+ - an empty "`[mode]`" matches if the attribute is set or has a value
+ - an empty "`[mode]`" combined with "`[=value]`" matches if the attribute has
+   the given value.
++
+
 exclude;;
 	After a path matches any non-exclude pathspec, it will be run
 	through all exclude pathspec (magic signature: `!`). If it
diff --git a/attr.c b/attr.c
index e0f7965..65cffd8 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 51ca36d..4bb4848 100644
--- a/attr.h
+++ b/attr.h
@@ -45,6 +45,8 @@ extern void git_attr_check_append(struct git_attr_check *, const struct git_attr
 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..3141a5a 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,48 @@ int within_depth(const char *name, int namelen,
 	return 1;
 }
 
+static int match_attrs(const char *name, int namelen,
+		       const struct pathspec_item *item)
+{
+	char *path;
+	int i;
+
+	path = xmemdupz(name, namelen);
+	git_check_attr(path, 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 ||
+				  match_mode == MATCH_SET_OR_VALUE ||
+				  match_mode == MATCH_NOT_UNSPECIFIED;
+		} else if (ATTR_FALSE(value)) {
+			matched = match_mode == MATCH_UNSET ||
+				  match_mode == MATCH_NOT_UNSPECIFIED;
+		} else if (ATTR_UNSET(value)) {
+			matched = match_mode == MATCH_UNSPECIFIED;
+		} else {
+			matched = match_mode == MATCH_NOT_UNSPECIFIED ||
+				  match_mode == MATCH_SET_OR_VALUE ||
+				  (match_mode == MATCH_VALUE &&
+				   !strcmp(item->attr_match[i].value, value));
+		}
+		if (!matched)
+			return 0;
+	}
+
+	free(path);
+
+	return 1;
+}
+
 #define DO_MATCH_EXCLUDE   1
 #define DO_MATCH_DIRECTORY 2
 
@@ -270,6 +313,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..32fb6a8 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,82 @@ 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))
+		goto err;
+
+	string_list_split(&list, value, ' ', -1);
+	string_list_remove_empty_items(&list, 0);
+
+	if (!item->attr_check)
+		item->attr_check = git_attr_check_alloc();
+
+	ALLOC_GROW(item->attr_match, item->attr_match_nr + list.nr, item->attr_match_alloc);
+
+	for_each_string_list_item(si, &list) {
+		size_t val_len;
+
+		int j = item->attr_match_nr++;
+		const char *val = si->string;
+		struct attr_match *am = &item->attr_match[j];
+
+		if (val[0] == '?')
+			am->match_mode = MATCH_NOT_UNSPECIFIED;
+		else if (val[0] == '~')
+			am->match_mode = MATCH_UNSPECIFIED;
+		else if (val[0] == '+')
+			am->match_mode = MATCH_SET;
+		else if (val[0] == '-')
+			am->match_mode = MATCH_UNSET;
+		else
+			am->match_mode = MATCH_SET_OR_VALUE;
+
+		if (am->match_mode != MATCH_SET_OR_VALUE)
+			/* skip first character */
+			val++;
+
+		val_len = strcspn(val, "=,)");
+		if (val[val_len] == '=') {
+			am->match_mode = MATCH_VALUE;
+			am->value = xstrdup(&val[val_len + 1]);
+			/*
+			 * NEEDSWORK:
+			 * Do we want to allow escaped commas to search
+			 * for comma separated values?
+			 */
+			if (strchr(am->value, '\\'))
+				die(_("attr spec values must not contain backslashes"));
+		} else
+			am->value = NULL;
+
+		if (invalid_attr_name(val, val_len)) {
+			am->match_mode = INVALID_ATTR;
+			goto err;
+		}
+
+		am->attr = git_attr(xmemdupz(val, val_len));
+		git_attr_check_append(item->attr_check, am->attr);
+	}
+
+	string_list_clear(&list, 0);
+	return;
+err:
+	die(_("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 +179,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 *pass = xmemdupz(body, len - strlen("attr:"));
+			parse_pathspec_attr_match(item, pass);
+			free(pass);
+			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 +502,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 +527,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 +589,14 @@ 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_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..35b3ab2
--- /dev/null
+++ b/t/t6134-pathspec-with-labels.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+
+test_description='test labels in pathspecs'
+. ./test-lib.sh
+
+test_expect_success 'setup a tree' '
+	mkdir sub &&
+	for p in fileA fileB fileC fileAB fileAC fileBC fileNoLabel fileUnsetLabel fileSetLabel fileValue fileWrongLabel; do
+		: >$p &&
+		git add $p &&
+		: >sub/$p
+		git add sub/$p
+	done &&
+	git commit -m $p &&
+	git ls-files >actual &&
+	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
+	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"
+'
+
+sq="'"
+
+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 set or value attr' '
+	cat <<EOF >expect &&
+fileSetLabel
+fileValue
+sub/fileSetLabel
+sub/fileValue
+EOF
+	git ls-files ":(attr:label)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'check unspecified attr' '
+	cat <<EOF >expect &&
+.gitattributes
+fileC
+fileNoLabel
+fileWrongLabel
+sub/fileC
+sub/fileNoLabel
+sub/fileWrongLabel
+EOF
+	git ls-files ":(attr:~label,attr:~labelA,attr:~labelB)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'check not unspecified attr' '
+	cat <<EOF >expect &&
+fileSetLabel
+fileUnsetLabel
+fileValue
+sub/fileSetLabel
+sub/fileUnsetLabel
+sub/fileValue
+EOF
+	git ls-files ":(attr:?label)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'check label with 2 labels' '
+	cat <<EOF >expect &&
+fileAB
+sub/fileAB
+EOF
+	git ls-files ":(attr:labelA labelB)" >actual &&
+	test_cmp expect actual &&
+	git ls-files ":(attr:labelA,attr:labelB)" >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.121.ga97fb08

[PATCHv7 3/5] pathspec: move long magic parsing out of prefix_pathspec

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:27

`prefix_pathspec` is quite a lengthy function and we plan on adding more.
Split it up for better readability. As we want to add code into the
inner loop of the long magic parsing, we also benefit from lower
indentation.

Signed-off-by: Stefan Beller <redacted>
---
 pathspec.c | 84 +++++++++++++++++++++++++++++++++++---------------------------
 1 file changed, 47 insertions(+), 37 deletions(-)
diff --git a/pathspec.c b/pathspec.c
index c9e9b6c..eba37c2 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -88,6 +88,52 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
 	strbuf_addf(sb, ",prefix:%d)", prefixlen);
 }
 
+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_;
+	/* longhand */
+	const char *nextat;
+	for (copyfrom = elt + 2;
+	     *copyfrom && *copyfrom != ')';
+	     copyfrom = nextat) {
+		size_t len = strcspn(copyfrom, ",)");
+		if (copyfrom[len] == ',')
+			nextat = copyfrom + len + 1;
+		else
+			/* handle ')' and '\0' */
+			nextat = copyfrom + len;
+		if (!len)
+			continue;
+		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+			if (strlen(pathspec_magic[i].name) == len &&
+			    !strncmp(pathspec_magic[i].name, copyfrom, len)) {
+				*magic |= pathspec_magic[i].bit;
+				break;
+			}
+			if (starts_with(copyfrom, "prefix:")) {
+				char *endptr;
+				*pathspec_prefix = strtol(copyfrom + 7,
+							  &endptr, 10);
+				if (endptr - copyfrom != len)
+					die(_("invalid parameter for pathspec magic 'prefix'"));
+				/* "i" would be wrong, but it does not matter */
+				break;
+			}
+		}
+		if (ARRAY_SIZE(pathspec_magic) <= i)
+			die(_("Invalid pathspec magic '%.*s' in '%s'"),
+			    (int) len, copyfrom, elt);
+	}
+	if (*copyfrom != ')')
+		die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
+	*long_magic_end = copyfrom;
+	copyfrom++;
+	*copyfrom_ = copyfrom;
+}
+
 /*
  * Take an element of a pathspec and check for magic signatures.
  * Append the result to the prefix. Return the magic bitmap.
@@ -150,43 +196,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
 	    (flags & PATHSPEC_LITERAL_PATH)) {
 		; /* nothing to do */
 	} else if (elt[1] == '(') {
-		/* longhand */
-		const char *nextat;
-		for (copyfrom = elt + 2;
-		     *copyfrom && *copyfrom != ')';
-		     copyfrom = nextat) {
-			size_t len = strcspn(copyfrom, ",)");
-			if (copyfrom[len] == ',')
-				nextat = copyfrom + len + 1;
-			else
-				/* handle ')' and '\0' */
-				nextat = copyfrom + len;
-			if (!len)
-				continue;
-			for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
-				if (strlen(pathspec_magic[i].name) == len &&
-				    !strncmp(pathspec_magic[i].name, copyfrom, len)) {
-					magic |= pathspec_magic[i].bit;
-					break;
-				}
-				if (starts_with(copyfrom, "prefix:")) {
-					char *endptr;
-					pathspec_prefix = strtol(copyfrom + 7,
-								 &endptr, 10);
-					if (endptr - copyfrom != len)
-						die(_("invalid parameter for pathspec magic 'prefix'"));
-					/* "i" would be wrong, but it does not matter */
-					break;
-				}
-			}
-			if (ARRAY_SIZE(pathspec_magic) <= i)
-				die(_("Invalid pathspec magic '%.*s' in '%s'"),
-				    (int) len, copyfrom, elt);
-		}
-		if (*copyfrom != ')')
-			die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
-		long_magic_end = copyfrom;
-		copyfrom++;
+		eat_long_magic(item, elt, &magic, &pathspec_prefix, &copyfrom, &long_magic_end);
 	} else {
 		/* shorthand */
 		for (copyfrom = elt + 1;
-- 
2.8.2.121.ga97fb08

[PATCHv7 4/5] pathspec: move prefix check out of the inner loop

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:27

The prefix check is not related the check of pathspec magic; also there
is no code that is relevant after we'd break the loop on a match for
"prefix:". So move the check before the loop and shortcircuit the outer
loop.

Signed-off-by: Stefan Beller <redacted>
---
 pathspec.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/pathspec.c b/pathspec.c
index eba37c2..4dff252 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -107,21 +107,22 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,
 			nextat = copyfrom + len;
 		if (!len)
 			continue;
+
+		if (starts_with(copyfrom, "prefix:")) {
+			char *endptr;
+			*pathspec_prefix = strtol(copyfrom + 7,
+						  &endptr, 10);
+			if (endptr - copyfrom != len)
+				die(_("invalid parameter for pathspec magic 'prefix'"));
+			continue;
+		}
+
 		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
 			if (strlen(pathspec_magic[i].name) == len &&
 			    !strncmp(pathspec_magic[i].name, copyfrom, len)) {
 				*magic |= pathspec_magic[i].bit;
 				break;
 			}
-			if (starts_with(copyfrom, "prefix:")) {
-				char *endptr;
-				*pathspec_prefix = strtol(copyfrom + 7,
-							  &endptr, 10);
-				if (endptr - copyfrom != len)
-					die(_("invalid parameter for pathspec magic 'prefix'"));
-				/* "i" would be wrong, but it does not matter */
-				break;
-			}
 		}
 		if (ARRAY_SIZE(pathspec_magic) <= i)
 			die(_("Invalid pathspec magic '%.*s' in '%s'"),
-- 
2.8.2.121.ga97fb08

Re: [PATCHv7 5/5] pathspec: allow querying for attributes

From: Stefan Beller <hidden>
Date: 2016-06-16 02:19:27

On Wed, May 18, 2016 at 12:02 PM, Stefan Beller [off-list ref] wrote:
 void free_pathspec(struct pathspec *pathspec)
 {
+       int i, j;
+       for (i = 0; i < pathspec->nr; i++) {
+               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);
This is faulty as may be NULL and git_attr_check_free assumes its
argument is not
NULL. So I'll add a guard here in a reoll.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help