Subject: [RFC/PATCH] pathspec: allow escaped query values

From: Stefan Beller <[email-masked]>
Date: Wed, 1 Jun 2016 16:52:33 -0700

In our own .gitattributes file we have attributes such as:

    *.[ch] whitespace=indent,trail,space

When querying for attributes we want to be able to ask for the exact
value, i.e.

    git ls-files :(attr:whitespace=indent,trail,space)

should work, but the commas are used in the attr magic to introduce
the next attr, such that this query currently fails with

fatal: Invalid pathspec magic 'trail' in ':(attr:whitespace=indent,trail,space)'

This change allows escaping characters by a backslash, such that the query

    git ls-files :(attr:whitespace=indent\,trail\,space)

will match all path that have the value "indent,trail,space" for the
whitespace attribute. To accomplish this, we need to modify two places.
First `eat_long_magic` needs to not stop early upon seeing a comma or
closing paren that is escaped. As a second step we need to remove any
escaping from the attr value. For now we just remove any backslashes.

Caveat: This doesn't allow for querying for values that have backslashes
in them, e.g.

    git ls-files :(attr:backslashes=\\)

that would ask for matches that have the `backslashes` value set to '\'.

Signed-off-by: Stefan Beller <[email-masked]>
---

 * This applies on top of sb/pathspec-label
 * Junio does this come close to what you imagine for escaped commas?
 
 Thanks,
 Stefan
  
 pathspec.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/pathspec.c b/pathspec.c
index 0a02255..925f949 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -89,6 +89,22 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
 	strbuf_addf(sb, ",prefix:%d)", prefixlen);
 }
 
+static char *attr_value_unquote(const char *value)
+{
+	char *ret = xstrdup(value);
+
+	size_t value_len = strlen(ret);
+	size_t len = strcspn(ret, "\\");
+
+	while (len != value_len) {
+		memmove(ret + len, ret + len + 1, value_len - len);
+		value_len--;
+		len += strcspn(ret + len, "\\");
+	}
+
+	return ret;
+}
+
 static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)
 {
 	struct string_list_item *si;
@@ -132,7 +148,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va
 				am->match_mode = MATCH_SET;
 			else {
 				am->match_mode = MATCH_VALUE;
-				am->value = xstrdup(&attr[attr_len + 1]);
+				am->value = attr_value_unquote(&attr[attr_len + 1]);
 				if (strchr(am->value, '\\'))
 					die(_("attr spec values must not contain backslashes"));
 			}
@@ -167,6 +183,9 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,
 	     *copyfrom && *copyfrom != ')';
 	     copyfrom = nextat) {
 		size_t len = strcspn(copyfrom, ",)");
+		while (len > 0 && copyfrom[len - 1] == '\\'
+		       && (copyfrom[len] == ',' || copyfrom[len] == ')'))
+			len += strcspn(copyfrom + len + 1, ",)") + 1;
 		if (copyfrom[len] == ',')
 			nextat = copyfrom + len + 1;
 		else
-- 
2.8.2.124.g24a9db3