1
1
#include "cache.h"
2
2
#include "dir.h"
3
3
#include "pathspec.h"
4
+ #include "attr.h"
4
5
5
6
/*
6
7
* Finds which of the given pathspecs match items in the index.
@@ -72,6 +73,7 @@ static struct pathspec_magic {
72
73
{ PATHSPEC_GLOB , '\0' , "glob" },
73
74
{ PATHSPEC_ICASE , '\0' , "icase" },
74
75
{ PATHSPEC_EXCLUDE , '!' , "exclude" },
76
+ { PATHSPEC_ATTR , '\0' , "attr" },
75
77
};
76
78
77
79
static 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)
87
89
strbuf_addf (sb , ",prefix:%d)" , prefixlen );
88
90
}
89
91
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
+
90
202
static inline int get_literal_global (void )
91
203
{
92
204
static int literal = -1 ;
@@ -164,13 +276,14 @@ static int get_global_magic(int element_magic)
164
276
* returns the position in 'elem' after all magic has been parsed
165
277
*/
166
278
static const char * parse_long_magic (unsigned * magic , int * prefix_len ,
279
+ struct pathspec_item * item ,
167
280
const char * elem )
168
281
{
169
282
const char * pos ;
170
283
const char * nextat ;
171
284
172
285
for (pos = elem + 2 ; * pos && * pos != ')' ; pos = nextat ) {
173
- size_t len = strcspn (pos , ",)" );
286
+ size_t len = strcspn_escaped (pos , ",)" );
174
287
int i ;
175
288
176
289
if (pos [len ] == ',' )
@@ -189,6 +302,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len,
189
302
continue ;
190
303
}
191
304
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
+
192
313
for (i = 0 ; i < ARRAY_SIZE (pathspec_magic ); i ++ ) {
193
314
if (strlen (pathspec_magic [i ].name ) == len &&
194
315
!strncmp (pathspec_magic [i ].name , pos , len )) {
@@ -252,13 +373,14 @@ static const char *parse_short_magic(unsigned *magic, const char *elem)
252
373
}
253
374
254
375
static const char * parse_element_magic (unsigned * magic , int * prefix_len ,
376
+ struct pathspec_item * item ,
255
377
const char * elem )
256
378
{
257
379
if (elem [0 ] != ':' || get_literal_global ())
258
380
return elem ; /* nothing to do */
259
381
else if (elem [1 ] == '(' )
260
382
/* longhand */
261
- return parse_long_magic (magic , prefix_len , elem );
383
+ return parse_long_magic (magic , prefix_len , item , elem );
262
384
else
263
385
/* shorthand */
264
386
return parse_short_magic (magic , elem );
@@ -335,12 +457,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
335
457
char * match ;
336
458
int pathspec_prefix = -1 ;
337
459
460
+ item -> attr_check = NULL ;
461
+ item -> attr_match = NULL ;
462
+ item -> attr_match_nr = 0 ;
463
+
338
464
/* PATHSPEC_LITERAL_PATH ignores magic */
339
465
if (flags & PATHSPEC_LITERAL_PATH ) {
340
466
magic = PATHSPEC_LITERAL ;
341
467
} else {
342
468
copyfrom = parse_element_magic (& element_magic ,
343
469
& pathspec_prefix ,
470
+ item ,
344
471
elt );
345
472
magic |= element_magic ;
346
473
magic |= get_global_magic (element_magic );
@@ -565,26 +692,46 @@ void parse_pathspec(struct pathspec *pathspec,
565
692
566
693
void copy_pathspec (struct pathspec * dst , const struct pathspec * src )
567
694
{
568
- int i ;
695
+ int i , j ;
569
696
570
697
* dst = * src ;
571
698
ALLOC_ARRAY (dst -> items , dst -> nr );
572
699
COPY_ARRAY (dst -> items , src -> items , dst -> nr );
573
700
574
701
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 );
577
716
}
578
717
}
579
718
580
719
void clear_pathspec (struct pathspec * pathspec )
581
720
{
582
- int i ;
721
+ int i , j ;
583
722
584
723
for (i = 0 ; i < pathspec -> nr ; i ++ ) {
585
724
free (pathspec -> items [i ].match );
586
725
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 );
587
733
}
734
+
588
735
free (pathspec -> items );
589
736
pathspec -> items = NULL ;
590
737
pathspec -> nr = 0 ;
0 commit comments