From mboxrd@z Thu Jan 1 00:00:00 1970 From: Stefan Beller Subject: [PATCHv8 5/5] pathspec: allow querying for attributes Date: Wed, 18 May 2016 18:09:35 -0700 Message-ID: <20160519010935.27856-6-sbeller@google.com> References: <20160519010935.27856-1-sbeller@google.com> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: QUOTED-PRINTABLE Cc: git@vger.kernel.org, Stefan Beller To: gitster@pobox.com, pclouds@gmail.com X-From: git-owner@vger.kernel.org Thu May 19 03:10:00 2016 Return-path: Envelope-to: gcvg-git-2@plane.gmane.org Received: from vger.kernel.org ([209.132.180.67]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1b3CTP-00057i-Ji for gcvg-git-2@plane.gmane.org; Thu, 19 May 2016 03:10:00 +0200 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752579AbcESBJ4 convert rfc822-to-quoted-printable (ORCPT ); Wed, 18 May 2016 21:09:56 -0400 Received: from mail-pa0-f51.google.com ([209.85.220.51]:34415 "EHLO mail-pa0-f51.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752385AbcESBJy (ORCPT ); Wed, 18 May 2016 21:09:54 -0400 Received: by mail-pa0-f51.google.com with SMTP id qo8so23338635pab.1 for ; Wed, 18 May 2016 18:09:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Hl/TnSVSqkNAoYia7YEejrAyvyUO17CH7pGI7AV1lY8=; b=FULc0+Hhm6+OIMJ90Vw0pqjJ9+k+9OGFEiPVsP+rOw0MODvRBlXT35NxnqmpxNJKY7 UIsHcnLEoZjS7Jn/BZBnTE/2xb1SLSYn/iyCERxd+FGYCWAKCqq0CDqYJapSvzacG9+B T5YS4nU0g8tY7WMe38FNgXcB2pOl3cKEnC9bZDVubGeNbeYkwJdNHDZ/33ezPSUFGG7+ hcYMeGPJxrgRhaCe8qAu6l34LHU2QVgd8xHo03hXZ43GYpv8a0j5gKqXWFT91XhrZKCK H2jLkAi1x5OSzii4Iwj2M/2BJ3Jr75+WH5iHkjEGUKGvqp+dh/wp4ZEUQU42rmxdYtV1 SO3w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Hl/TnSVSqkNAoYia7YEejrAyvyUO17CH7pGI7AV1lY8=; b=TaBWtpb4V8Nrlv+tKr1g5QESuiKaOns8MUvneCDjenuXEBEIbeBoQvK6d6w0BlU1fA 4w3bw5SwL/8lghh+PRrQIw4UvZgNmQcjjJylcBVcTfi+t/QUYR9NqRXGkAs2hze9vfaK kvsq52kyCf4VIkI95k6IGzbZCQ/3eqFy+frA1ubCZbS1GY/9NWkMR4vpltDXkiq1XnnO 0pd2yBjDXpg56uwwYC/GJfGc0QFQRA5HxwJPhR1uwrigkEjH3bw4eyGbH69SRN65JZuW Hwycq9Rkovmt3Jl7XJ624hFCsmS/iRuq0+D3ldCtSSnaNpAZcJK0YD+j07HA4H43QnZ1 /5OQ== X-Gm-Message-State: AOPr4FUqQ78IBBVFoVkljms2ph6d5AFCID8wOXYZe3DNQLdHlQy2b3EtS20GCywuvu/vklrM X-Received: by 10.66.84.164 with SMTP id a4mr15256288paz.90.1463620193367; Wed, 18 May 2016 18:09:53 -0700 (PDT) Received: from localhost ([2620:0:1000:5b10:d468:afb3:b0f0:947e]) by smtp.gmail.com with ESMTPSA id ey12sm15013286pac.26.2016.05.18.18.09.52 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Wed, 18 May 2016 18:09:52 -0700 (PDT) X-Mailer: git-send-email 2.8.2.123.g3bde101 In-Reply-To: <20160519010935.27856-1-sbeller@google.com> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Archived-At: The pathspec mechanism is extended via the new ":(attr:eol=3Dinput)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 --- Documentation/glossary-content.txt | 17 ++++ dir.c | 35 ++++++++ pathspec.c | 97 +++++++++++++++++++++- pathspec.h | 16 ++++ t/t6134-pathspec-with-labels.sh | 166 +++++++++++++++++++++++++++++= ++++++++ 5 files changed, 327 insertions(+), 4 deletions(-) create mode 100755 t/t6134-pathspec-with-labels.sh diff --git a/Documentation/glossary-content.txt b/Documentation/glossar= y-content.txt index cafc284..aa9f220 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -384,6 +384,23 @@ full pathname may have special meaning: + Glob magic is incompatible with literal magic. =20 +attr;; + Additionally to matching the pathspec, the path must have the + attribute as specified. The syntax for specifying the required + attributes is "`attr: [mode] [=3Dvalue]`" ++ +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 s= pecial +character to indicate which attribute states are looked for. The follo= wing +modes are available: + + - an empty "`[mode]`" matches if the attribute is set + - "`-`" the attribute must be unset + - "`!`" the attribute must be unspecified + - an empty "`[mode]`" combined with "`[=3Dvalue]`" matches if the att= ribute 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/dir.c b/dir.c index 996653b..f60a503 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; } =20 +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 =3D 0; i < item->attr_match_nr; i++) { + const char *value; + int matched; + enum attr_match_mode match_mode; + + value =3D item->attr_check->check[i].value; + match_mode =3D item->attr_match[i].match_mode; + + if (ATTR_TRUE(value)) + matched =3D match_mode =3D=3D MATCH_SET; + else if (ATTR_FALSE(value)) + matched =3D match_mode =3D=3D MATCH_UNSET; + else if (ATTR_UNSET(value)) + matched =3D match_mode =3D=3D MATCH_UNSPECIFIED; + else + matched =3D (match_mode =3D=3D 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 =20 @@ -270,6 +302,9 @@ static int match_pathspec_item(const struct pathspe= c_item *item, int prefix, strncmp(item->match, name - prefix, item->prefix)) return 0; =20 + 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..b795a9c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,6 +1,7 @@ #include "cache.h" #include "dir.h" #include "pathspec.h" +#include "attr.h" =20 /* * Finds which of the given pathspecs match items in the index. @@ -88,12 +89,74 @@ static void prefix_short_magic(struct strbuf *sb, i= nt prefixlen, strbuf_addf(sb, ",prefix:%d)", prefixlen); } =20 +static void parse_pathspec_attr_match(struct pathspec_item *item, cons= t char *value) +{ + struct string_list_item *si; + struct string_list list =3D 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 =3D git_attr_check_alloc(); + else + die(_("Only one 'attr:' specification is allowed.")); + + ALLOC_GROW(item->attr_match, item->attr_match_nr + list.nr, item->att= r_match_alloc); + + for_each_string_list_item(si, &list) { + size_t attr_len; + + int j =3D item->attr_match_nr++; + const char *attr =3D si->string; + struct attr_match *am =3D &item->attr_match[j]; + + if (attr[0] =3D=3D '!') + am->match_mode =3D MATCH_UNSPECIFIED; + else if (attr[0] =3D=3D '-') + am->match_mode =3D MATCH_UNSET; + else + am->match_mode =3D MATCH_SET; + + if (am->match_mode !=3D MATCH_SET) + /* skip first character */ + attr++; + + attr_len =3D strcspn(attr, "=3D"); + if (attr[attr_len] =3D=3D '=3D') { + am->match_mode =3D MATCH_VALUE; + am->value =3D xstrdup(&attr[attr_len + 1]); + if (strchr(am->value, '\\')) + die(_("attr spec values must not contain backslashes")); + } else + am->value =3D NULL; + + if (!attr_name_valid(attr, attr_len)) { + struct strbuf sb =3D STRBUF_INIT; + am->match_mode =3D INVALID_ATTR; + invalid_attr_name_message(&sb, attr, attr_len); + die(_("invalid attribute in '%s': '%s'"), value, sb.buf); + } + + am->attr =3D git_attr_counted(attr, attr_len); + 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 =3D *copyfrom_; + const char *body; /* longhand */ const char *nextat; for (copyfrom =3D elt + 2; @@ -108,15 +171,21 @@ static void eat_long_magic(struct pathspec_item *= item, const char *elt, if (!len) continue; =20 - if (starts_with(copyfrom, "prefix:")) { + if (skip_prefix(copyfrom, "prefix:", &body)) { char *endptr; - *pathspec_prefix =3D strtol(copyfrom + 7, - &endptr, 10); + *pathspec_prefix =3D strtol(body, &endptr, 10); if (endptr - copyfrom !=3D len) die(_("invalid parameter for pathspec magic 'prefix'")); continue; } =20 + if (skip_prefix(copyfrom, "attr:", &body)) { + char *attr_body =3D xmemdupz(body, len - strlen("attr:")); + parse_pathspec_attr_match(item, attr_body); + free(attr_body); + continue; + } + for (i =3D 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) =3D=3D len && !strncmp(pathspec_magic[i].name, copyfrom, len)) { @@ -425,7 +494,10 @@ void parse_pathspec(struct pathspec *pathspec, for (i =3D 0; i < n; i++) { unsigned short_magic; entry =3D argv[i]; - + item[i].attr_check =3D NULL; + item[i].attr_match =3D NULL; + item[i].attr_match_nr =3D 0; + item[i].attr_match_alloc =3D 0; item[i].magic =3D prefix_pathspec(item + i, &short_magic, argv + i, flags, prefix, prefixlen, entry); @@ -447,6 +519,13 @@ void parse_pathspec(struct pathspec *pathspec, if (item[i].nowildcard_len < item[i].len) pathspec->has_wildcard =3D 1; pathspec->magic |=3D item[i].magic; + + if (item[i].attr_match_nr) { + int j; + for (j =3D 0; j < item[i].attr_match_nr; j++) + if (item[i].attr_match[j].match_mode =3D=3D INVALID_ATTR) + die(_("attribute spec in the wrong syntax are prohibited.")); + } } =20 if (nr_exclude =3D=3D n) @@ -502,6 +581,16 @@ void copy_pathspec(struct pathspec *dst, const str= uct pathspec *src) =20 void free_pathspec(struct pathspec *pathspec) { + int i, j; + for (i =3D 0; i < pathspec->nr; i++) { + if (!pathspec->items[i].attr_match_nr) + continue; + for (j =3D 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 =3D 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; }; =20 diff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-la= bels.sh new file mode 100755 index 0000000..c0d8cda --- /dev/null +++ b/t/t6134-pathspec-with-labels.sh @@ -0,0 +1,166 @@ +#!/bin/sh + +test_description=3D'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 fileUnset= Label fileSetLabel fileValue fileWrongLabel; do + : >$p && + git add $p && + : >sub/$p + git add sub/$p + done && + git commit -m $p && + git ls-files >actual && + cat <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 .gitattribu= tes' ' + git ls-files ":(attr:label)" >actual && + test_must_be_empty actual +' + +test_expect_success 'setup .gitattributes' ' + cat <.gitattributes && +fileA labelA +fileB labelB +fileC labelC +fileAB labelA labelB +fileAC labelA labelC +fileBC labelB labelC +fileUnsetLabel -label +fileSetLabel label +fileValue label=3Dfoo +fileWrongLabel label=E2=98=BA +EOF + git add .gitattributes && + git commit -m "add attributes" +' + +sq=3D"'" + +test_expect_success 'check specific set attr' ' + cat <expect && +fileSetLabel +sub/fileSetLabel +EOF + git ls-files ":(attr:label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific unset attr' ' + cat <expect && +fileUnsetLabel +sub/fileUnsetLabel +EOF + git ls-files ":(attr:-label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific value attr' ' + cat <expect && +fileValue +sub/fileValue +EOF + git ls-files ":(attr:label=3Dfoo)" >actual && + test_cmp expect actual && + git ls-files ":(attr:label=3Dbar)" >actual && + test_must_be_empty actual +' + +test_expect_success 'check unspecified attr' ' + cat <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 <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 <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 <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:=E2=98=BA)" 2>actual && + test_i18ngrep "fatal" actual +' + +test_done --=20 2.8.2.123.g3bde101