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,72 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
8789 strbuf_addf (sb , ",prefix:%d)" , prefixlen );
8890}
8991
92+ static void parse_pathspec_attr_match (struct pathspec_item * item , const char * value )
93+ {
94+ struct string_list_item * si ;
95+ struct string_list list = STRING_LIST_INIT_DUP ;
96+
97+ if (item -> attr_check || item -> attr_match )
98+ die (_ ("Only one 'attr:' specification is allowed." ));
99+
100+ if (!value || !* value )
101+ die (_ ("attr spec must not be empty" ));
102+
103+ string_list_split (& list , value , ' ' , -1 );
104+ string_list_remove_empty_items (& list , 0 );
105+
106+ item -> attr_check = attr_check_alloc ();
107+ item -> attr_match = xcalloc (list .nr , sizeof (struct attr_match ));
108+
109+ for_each_string_list_item (si , & list ) {
110+ size_t attr_len ;
111+ char * attr_name ;
112+ const struct git_attr * a ;
113+
114+ int j = item -> attr_match_nr ++ ;
115+ const char * attr = si -> string ;
116+ struct attr_match * am = & item -> attr_match [j ];
117+
118+ switch (* attr ) {
119+ case '!' :
120+ am -> match_mode = MATCH_UNSPECIFIED ;
121+ attr ++ ;
122+ attr_len = strlen (attr );
123+ break ;
124+ case '-' :
125+ am -> match_mode = MATCH_UNSET ;
126+ attr ++ ;
127+ attr_len = strlen (attr );
128+ break ;
129+ default :
130+ attr_len = strcspn (attr , "=" );
131+ if (attr [attr_len ] != '=' )
132+ am -> match_mode = MATCH_SET ;
133+ else {
134+ am -> match_mode = MATCH_VALUE ;
135+ am -> value = xstrdup (& attr [attr_len + 1 ]);
136+ if (strchr (am -> value , '\\' ))
137+ die (_ ("attr spec values must not contain backslashes" ));
138+ }
139+ break ;
140+ }
141+
142+ attr_name = xmemdupz (attr , attr_len );
143+ a = git_attr (attr_name );
144+ if (!a )
145+ die (_ ("invalid attribute name %s" ), attr_name );
146+
147+ attr_check_append (item -> attr_check , a );
148+
149+ free (attr_name );
150+ }
151+
152+ if (item -> attr_check -> nr != item -> attr_match_nr )
153+ die ("BUG: should have same number of entries" );
154+
155+ string_list_clear (& list , 0 );
156+ }
157+
90158static inline int get_literal_global (void )
91159{
92160 static int literal = -1 ;
@@ -164,6 +232,7 @@ static int get_global_magic(int element_magic)
164232 * returns the position in 'elem' after all magic has been parsed
165233 */
166234static const char * parse_long_magic (unsigned * magic , int * prefix_len ,
235+ struct pathspec_item * item ,
167236 const char * elem )
168237{
169238 const char * pos ;
@@ -189,6 +258,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len,
189258 continue ;
190259 }
191260
261+ if (starts_with (pos , "attr:" )) {
262+ char * attr_body = xmemdupz (pos + 5 , len - 5 );
263+ parse_pathspec_attr_match (item , attr_body );
264+ * magic |= PATHSPEC_ATTR ;
265+ free (attr_body );
266+ continue ;
267+ }
268+
192269 for (i = 0 ; i < ARRAY_SIZE (pathspec_magic ); i ++ ) {
193270 if (strlen (pathspec_magic [i ].name ) == len &&
194271 !strncmp (pathspec_magic [i ].name , pos , len )) {
@@ -252,13 +329,14 @@ static const char *parse_short_magic(unsigned *magic, const char *elem)
252329}
253330
254331static const char * parse_element_magic (unsigned * magic , int * prefix_len ,
332+ struct pathspec_item * item ,
255333 const char * elem )
256334{
257335 if (elem [0 ] != ':' || get_literal_global ())
258336 return elem ; /* nothing to do */
259337 else if (elem [1 ] == '(' )
260338 /* longhand */
261- return parse_long_magic (magic , prefix_len , elem );
339+ return parse_long_magic (magic , prefix_len , item , elem );
262340 else
263341 /* shorthand */
264342 return parse_short_magic (magic , elem );
@@ -335,12 +413,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
335413 char * match ;
336414 int pathspec_prefix = -1 ;
337415
416+ item -> attr_check = NULL ;
417+ item -> attr_match = NULL ;
418+ item -> attr_match_nr = 0 ;
419+
338420 /* PATHSPEC_LITERAL_PATH ignores magic */
339421 if (flags & PATHSPEC_LITERAL_PATH ) {
340422 magic = PATHSPEC_LITERAL ;
341423 } else {
342424 copyfrom = parse_element_magic (& element_magic ,
343425 & pathspec_prefix ,
426+ item ,
344427 elt );
345428 magic |= element_magic ;
346429 magic |= get_global_magic (element_magic );
@@ -565,26 +648,46 @@ void parse_pathspec(struct pathspec *pathspec,
565648
566649void copy_pathspec (struct pathspec * dst , const struct pathspec * src )
567650{
568- int i ;
651+ int i , j ;
569652
570653 * dst = * src ;
571654 ALLOC_ARRAY (dst -> items , dst -> nr );
572655 COPY_ARRAY (dst -> items , src -> items , dst -> nr );
573656
574657 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 );
658+ struct pathspec_item * d = & dst -> items [i ];
659+ struct pathspec_item * s = & src -> items [i ];
660+
661+ d -> match = xstrdup (s -> match );
662+ d -> original = xstrdup (s -> original );
663+
664+ ALLOC_ARRAY (d -> attr_match , d -> attr_match_nr );
665+ COPY_ARRAY (d -> attr_match , s -> attr_match , d -> attr_match_nr );
666+ for (j = 0 ; j < d -> attr_match_nr ; j ++ ) {
667+ const char * value = s -> attr_match [j ].value ;
668+ d -> attr_match [j ].value = xstrdup_or_null (value );
669+ }
670+
671+ d -> attr_check = attr_check_dup (s -> attr_check );
577672 }
578673}
579674
580675void clear_pathspec (struct pathspec * pathspec )
581676{
582- int i ;
677+ int i , j ;
583678
584679 for (i = 0 ; i < pathspec -> nr ; i ++ ) {
585680 free (pathspec -> items [i ].match );
586681 free (pathspec -> items [i ].original );
682+
683+ for (j = 0 ; j < pathspec -> items [j ].attr_match_nr ; j ++ )
684+ free (pathspec -> items [i ].attr_match [j ].value );
685+ free (pathspec -> items [i ].attr_match );
686+
687+ if (pathspec -> items [i ].attr_check )
688+ attr_check_free (pathspec -> items [i ].attr_check );
587689 }
690+
588691 free (pathspec -> items );
589692 pathspec -> items = NULL ;
590693 pathspec -> nr = 0 ;
0 commit comments