11#include "cache.h"
22#include "dir.h"
33#include "pathspec.h"
4+ #include "attr.h"
45
56/*
67 * Finds which of the given pathspecs match items in the index.
@@ -72,6 +73,7 @@ static struct pathspec_magic {
7273 { PATHSPEC_GLOB , '\0' , "glob" },
7374 { PATHSPEC_ICASE , '\0' , "icase" },
7475 { PATHSPEC_EXCLUDE , '!' , "exclude" },
76+ { PATHSPEC_ATTR , '\0' , "attr" },
7577};
7678
7779static void prefix_magic (struct strbuf * sb , int prefixlen , unsigned magic )
@@ -87,6 +89,116 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
8789 strbuf_addf (sb , ",prefix:%d)" , prefixlen );
8890}
8991
92+ static size_t strcspn_escaped (const char * s , const char * stop )
93+ {
94+ const char * i ;
95+
96+ for (i = s ; * i ; i ++ ) {
97+ /* skip the escaped character */
98+ if (i [0 ] == '\\' && i [1 ]) {
99+ i ++ ;
100+ continue ;
101+ }
102+
103+ if (strchr (stop , * i ))
104+ break ;
105+ }
106+ return i - s ;
107+ }
108+
109+ static inline int invalid_value_char (const char ch )
110+ {
111+ if (isalnum (ch ) || strchr (",-_" , ch ))
112+ return 0 ;
113+ return -1 ;
114+ }
115+
116+ static char * attr_value_unescape (const char * value )
117+ {
118+ const char * src ;
119+ char * dst , * ret ;
120+
121+ ret = xmallocz (strlen (value ));
122+ for (src = value , dst = ret ; * src ; src ++ , dst ++ ) {
123+ if (* src == '\\' ) {
124+ if (!src [1 ])
125+ die (_ ("Escape character '\\' not allowed as "
126+ "last character in attr value" ));
127+ src ++ ;
128+ }
129+ if (invalid_value_char (* src ))
130+ die ("cannot use '%c' for value matching" , * src );
131+ * dst = * src ;
132+ }
133+ * dst = '\0' ;
134+ return ret ;
135+ }
136+
137+ static void parse_pathspec_attr_match (struct pathspec_item * item , const char * value )
138+ {
139+ struct string_list_item * si ;
140+ struct string_list list = STRING_LIST_INIT_DUP ;
141+
142+ if (item -> attr_check || item -> attr_match )
143+ die (_ ("Only one 'attr:' specification is allowed." ));
144+
145+ if (!value || !* value )
146+ die (_ ("attr spec must not be empty" ));
147+
148+ string_list_split (& list , value , ' ' , -1 );
149+ string_list_remove_empty_items (& list , 0 );
150+
151+ item -> attr_check = attr_check_alloc ();
152+ item -> attr_match = xcalloc (list .nr , sizeof (struct attr_match ));
153+
154+ for_each_string_list_item (si , & list ) {
155+ size_t attr_len ;
156+ char * attr_name ;
157+ const struct git_attr * a ;
158+
159+ int j = item -> attr_match_nr ++ ;
160+ const char * attr = si -> string ;
161+ struct attr_match * am = & item -> attr_match [j ];
162+
163+ switch (* attr ) {
164+ case '!' :
165+ am -> match_mode = MATCH_UNSPECIFIED ;
166+ attr ++ ;
167+ attr_len = strlen (attr );
168+ break ;
169+ case '-' :
170+ am -> match_mode = MATCH_UNSET ;
171+ attr ++ ;
172+ attr_len = strlen (attr );
173+ break ;
174+ default :
175+ attr_len = strcspn (attr , "=" );
176+ if (attr [attr_len ] != '=' )
177+ am -> match_mode = MATCH_SET ;
178+ else {
179+ const char * v = & attr [attr_len + 1 ];
180+ am -> match_mode = MATCH_VALUE ;
181+ am -> value = attr_value_unescape (v );
182+ }
183+ break ;
184+ }
185+
186+ attr_name = xmemdupz (attr , attr_len );
187+ a = git_attr (attr_name );
188+ if (!a )
189+ die (_ ("invalid attribute name %s" ), attr_name );
190+
191+ attr_check_append (item -> attr_check , a );
192+
193+ free (attr_name );
194+ }
195+
196+ if (item -> attr_check -> nr != item -> attr_match_nr )
197+ die ("BUG: should have same number of entries" );
198+
199+ string_list_clear (& list , 0 );
200+ }
201+
90202static inline int get_literal_global (void )
91203{
92204 static int literal = -1 ;
@@ -164,13 +276,14 @@ static int get_global_magic(int element_magic)
164276 * returns the position in 'elem' after all magic has been parsed
165277 */
166278static const char * parse_long_magic (unsigned * magic , int * prefix_len ,
279+ struct pathspec_item * item ,
167280 const char * elem )
168281{
169282 const char * pos ;
170283 const char * nextat ;
171284
172285 for (pos = elem + 2 ; * pos && * pos != ')' ; pos = nextat ) {
173- size_t len = strcspn (pos , ",)" );
286+ size_t len = strcspn_escaped (pos , ",)" );
174287 int i ;
175288
176289 if (pos [len ] == ',' )
@@ -189,6 +302,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len,
189302 continue ;
190303 }
191304
305+ if (starts_with (pos , "attr:" )) {
306+ char * attr_body = xmemdupz (pos + 5 , len - 5 );
307+ parse_pathspec_attr_match (item , attr_body );
308+ * magic |= PATHSPEC_ATTR ;
309+ free (attr_body );
310+ continue ;
311+ }
312+
192313 for (i = 0 ; i < ARRAY_SIZE (pathspec_magic ); i ++ ) {
193314 if (strlen (pathspec_magic [i ].name ) == len &&
194315 !strncmp (pathspec_magic [i ].name , pos , len )) {
@@ -252,13 +373,14 @@ static const char *parse_short_magic(unsigned *magic, const char *elem)
252373}
253374
254375static const char * parse_element_magic (unsigned * magic , int * prefix_len ,
376+ struct pathspec_item * item ,
255377 const char * elem )
256378{
257379 if (elem [0 ] != ':' || get_literal_global ())
258380 return elem ; /* nothing to do */
259381 else if (elem [1 ] == '(' )
260382 /* longhand */
261- return parse_long_magic (magic , prefix_len , elem );
383+ return parse_long_magic (magic , prefix_len , item , elem );
262384 else
263385 /* shorthand */
264386 return parse_short_magic (magic , elem );
@@ -335,12 +457,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
335457 char * match ;
336458 int pathspec_prefix = -1 ;
337459
460+ item -> attr_check = NULL ;
461+ item -> attr_match = NULL ;
462+ item -> attr_match_nr = 0 ;
463+
338464 /* PATHSPEC_LITERAL_PATH ignores magic */
339465 if (flags & PATHSPEC_LITERAL_PATH ) {
340466 magic = PATHSPEC_LITERAL ;
341467 } else {
342468 copyfrom = parse_element_magic (& element_magic ,
343469 & pathspec_prefix ,
470+ item ,
344471 elt );
345472 magic |= element_magic ;
346473 magic |= get_global_magic (element_magic );
@@ -565,26 +692,46 @@ void parse_pathspec(struct pathspec *pathspec,
565692
566693void copy_pathspec (struct pathspec * dst , const struct pathspec * src )
567694{
568- int i ;
695+ int i , j ;
569696
570697 * dst = * src ;
571698 ALLOC_ARRAY (dst -> items , dst -> nr );
572699 COPY_ARRAY (dst -> items , src -> items , dst -> nr );
573700
574701 for (i = 0 ; i < dst -> nr ; i ++ ) {
575- dst -> items [i ].match = xstrdup (src -> items [i ].match );
576- dst -> items [i ].original = xstrdup (src -> items [i ].original );
702+ struct pathspec_item * d = & dst -> items [i ];
703+ struct pathspec_item * s = & src -> items [i ];
704+
705+ d -> match = xstrdup (s -> match );
706+ d -> original = xstrdup (s -> original );
707+
708+ ALLOC_ARRAY (d -> attr_match , d -> attr_match_nr );
709+ COPY_ARRAY (d -> attr_match , s -> attr_match , d -> attr_match_nr );
710+ for (j = 0 ; j < d -> attr_match_nr ; j ++ ) {
711+ const char * value = s -> attr_match [j ].value ;
712+ d -> attr_match [j ].value = xstrdup_or_null (value );
713+ }
714+
715+ d -> attr_check = attr_check_dup (s -> attr_check );
577716 }
578717}
579718
580719void clear_pathspec (struct pathspec * pathspec )
581720{
582- int i ;
721+ int i , j ;
583722
584723 for (i = 0 ; i < pathspec -> nr ; i ++ ) {
585724 free (pathspec -> items [i ].match );
586725 free (pathspec -> items [i ].original );
726+
727+ for (j = 0 ; j < pathspec -> items [j ].attr_match_nr ; j ++ )
728+ free (pathspec -> items [i ].attr_match [j ].value );
729+ free (pathspec -> items [i ].attr_match );
730+
731+ if (pathspec -> items [i ].attr_check )
732+ attr_check_free (pathspec -> items [i ].attr_check );
587733 }
734+
588735 free (pathspec -> items );
589736 pathspec -> items = NULL ;
590737 pathspec -> nr = 0 ;
0 commit comments