]>
Commit | Line | Data |
---|---|---|
ee1f0b1a | 1 | #include "common.h" |
73b51450 | 2 | #include "repository.h" |
ee1f0b1a RB |
3 | #include "filebuf.h" |
4 | #include <ctype.h> | |
5 | ||
6 | const char *git_attr__true = "[internal]__TRUE__"; | |
7 | const char *git_attr__false = "[internal]__FALSE__"; | |
8 | ||
ee1f0b1a | 9 | static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); |
c6d2a2c0 | 10 | static void git_attr_rule__clear(git_attr_rule *rule); |
ee1f0b1a | 11 | |
df743c7d | 12 | int git_attr_file__new(git_attr_file **attrs_ptr) |
73b51450 | 13 | { |
df743c7d RB |
14 | int error; |
15 | git_attr_file *attrs = NULL; | |
16 | ||
17 | attrs = git__calloc(1, sizeof(git_attr_file)); | |
18 | if (attrs == NULL) | |
19 | error = GIT_ENOMEM; | |
20 | else | |
21 | error = git_vector_init(&attrs->rules, 4, NULL); | |
22 | ||
23 | if (error != GIT_SUCCESS) { | |
24 | git__rethrow(error, "Could not allocate attribute storage"); | |
25 | git__free(attrs); | |
26 | attrs = NULL; | |
27 | } | |
28 | ||
29 | *attrs_ptr = attrs; | |
73b51450 | 30 | |
df743c7d | 31 | return error; |
73b51450 RB |
32 | } |
33 | ||
a51cd8e6 RB |
34 | int git_attr_file__set_path( |
35 | git_repository *repo, const char *path, git_attr_file *file) | |
36 | { | |
37 | if (file->path != NULL) { | |
38 | git__free(file->path); | |
39 | file->path = NULL; | |
40 | } | |
41 | ||
42 | if (repo == NULL) | |
43 | file->path = git__strdup(path); | |
44 | else { | |
45 | const char *workdir = git_repository_workdir(repo); | |
46 | ||
47 | if (workdir && git__prefixcmp(path, workdir) == 0) | |
48 | file->path = git__strdup(path + strlen(workdir)); | |
49 | else | |
50 | file->path = git__strdup(path); | |
51 | } | |
52 | ||
53 | return (file->path == NULL) ? GIT_ENOMEM : GIT_SUCCESS; | |
54 | } | |
55 | ||
73b51450 | 56 | int git_attr_file__from_buffer( |
a51cd8e6 | 57 | git_repository *repo, const char *buffer, git_attr_file *attrs) |
ee1f0b1a RB |
58 | { |
59 | int error = GIT_SUCCESS; | |
ee1f0b1a | 60 | const char *scan = NULL; |
a51cd8e6 | 61 | char *context = NULL; |
ee1f0b1a RB |
62 | git_attr_rule *rule = NULL; |
63 | ||
a51cd8e6 | 64 | assert(buffer && attrs); |
ee1f0b1a RB |
65 | |
66 | scan = buffer; | |
67 | ||
a51cd8e6 RB |
68 | if (attrs->path && git__suffixcmp(attrs->path, GIT_ATTR_FILE) == 0) { |
69 | context = git__strndup(attrs->path, | |
70 | strlen(attrs->path) - strlen(GIT_ATTR_FILE)); | |
71 | if (!context) error = GIT_ENOMEM; | |
72 | } | |
73 | ||
ee1f0b1a RB |
74 | while (error == GIT_SUCCESS && *scan) { |
75 | /* allocate rule if needed */ | |
76 | if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { | |
77 | error = GIT_ENOMEM; | |
78 | break; | |
79 | } | |
80 | ||
81 | /* parse the next "pattern attr attr attr" line */ | |
a51cd8e6 | 82 | if (!(error = git_attr_fnmatch__parse(&rule->match, context, &scan)) && |
73b51450 RB |
83 | !(error = git_attr_assignment__parse(repo, &rule->assigns, &scan))) |
84 | { | |
85 | if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) | |
86 | /* should generate error/warning if this is coming from any | |
87 | * file other than .gitattributes at repo root. | |
88 | */ | |
89 | error = git_attr_cache__insert_macro(repo, rule); | |
90 | else | |
91 | error = git_vector_insert(&attrs->rules, rule); | |
92 | } | |
ee1f0b1a RB |
93 | |
94 | /* if the rule wasn't a pattern, on to the next */ | |
95 | if (error != GIT_SUCCESS) { | |
c6d2a2c0 | 96 | git_attr_rule__clear(rule); /* reset rule contents */ |
ee1f0b1a RB |
97 | if (error == GIT_ENOTFOUND) |
98 | error = GIT_SUCCESS; | |
99 | } else { | |
100 | rule = NULL; /* vector now "owns" the rule */ | |
101 | } | |
102 | } | |
103 | ||
a51cd8e6 RB |
104 | git_attr_rule__free(rule); |
105 | git__free(context); | |
ee1f0b1a RB |
106 | |
107 | return error; | |
108 | } | |
109 | ||
73b51450 | 110 | int git_attr_file__from_file( |
a51cd8e6 | 111 | git_repository *repo, const char *path, git_attr_file *file) |
ee1f0b1a RB |
112 | { |
113 | int error = GIT_SUCCESS; | |
114 | git_fbuffer fbuf = GIT_FBUFFER_INIT; | |
115 | ||
a51cd8e6 | 116 | assert(path && file); |
ee1f0b1a | 117 | |
a51cd8e6 RB |
118 | if (file->path == NULL) |
119 | error = git_attr_file__set_path(repo, path, file); | |
120 | ||
121 | if (error == GIT_SUCCESS && | |
122 | (error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS) | |
123 | error = git_attr_file__from_buffer(repo, fbuf.data, file); | |
ee1f0b1a RB |
124 | |
125 | git_futils_freebuffer(&fbuf); | |
a51cd8e6 RB |
126 | if (error != GIT_SUCCESS) |
127 | git__rethrow(error, "Could not open attribute file '%s'", path); | |
ee1f0b1a RB |
128 | |
129 | return error; | |
130 | } | |
131 | ||
132 | void git_attr_file__free(git_attr_file *file) | |
133 | { | |
134 | unsigned int i; | |
135 | git_attr_rule *rule; | |
136 | ||
137 | if (!file) | |
138 | return; | |
139 | ||
c6d2a2c0 | 140 | git_vector_foreach(&file->rules, i, rule) |
73b51450 | 141 | git_attr_rule__free(rule); |
ee1f0b1a RB |
142 | |
143 | git_vector_free(&file->rules); | |
144 | ||
145 | git__free(file->path); | |
146 | file->path = NULL; | |
c6d2a2c0 RB |
147 | |
148 | git__free(file); | |
ee1f0b1a RB |
149 | } |
150 | ||
151 | unsigned long git_attr_file__name_hash(const char *name) | |
152 | { | |
153 | unsigned long h = 5381; | |
154 | int c; | |
155 | assert(name); | |
156 | while ((c = (int)*name++) != 0) | |
157 | h = ((h << 5) + h) + c; | |
158 | return h; | |
159 | } | |
160 | ||
161 | ||
162 | int git_attr_file__lookup_one( | |
163 | git_attr_file *file, | |
164 | const git_attr_path *path, | |
165 | const char *attr, | |
166 | const char **value) | |
167 | { | |
168 | unsigned int i; | |
169 | git_attr_name name; | |
170 | git_attr_rule *rule; | |
171 | ||
172 | *value = NULL; | |
173 | ||
174 | name.name = attr; | |
175 | name.name_hash = git_attr_file__name_hash(attr); | |
176 | ||
177 | git_attr_file__foreach_matching_rule(file, path, i, rule) { | |
178 | int pos = git_vector_bsearch(&rule->assigns, &name); | |
179 | git_clearerror(); /* okay if search failed */ | |
180 | ||
181 | if (pos >= 0) { | |
182 | *value = ((git_attr_assignment *) | |
183 | git_vector_get(&rule->assigns, pos))->value; | |
184 | break; | |
185 | } | |
186 | } | |
187 | ||
188 | return GIT_SUCCESS; | |
189 | } | |
190 | ||
191 | ||
df743c7d RB |
192 | int git_attr_fnmatch__match( |
193 | git_attr_fnmatch *match, | |
ee1f0b1a RB |
194 | const git_attr_path *path) |
195 | { | |
196 | int matched = FNM_NOMATCH; | |
197 | ||
df743c7d | 198 | if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) |
ee1f0b1a RB |
199 | return matched; |
200 | ||
df743c7d RB |
201 | if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) |
202 | matched = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); | |
e4eb94a2 RB |
203 | else if (path->is_dir) |
204 | matched = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR); | |
ee1f0b1a | 205 | else |
df743c7d RB |
206 | matched = p_fnmatch(match->pattern, path->basename, 0); |
207 | ||
208 | return matched; | |
209 | } | |
210 | ||
211 | int git_attr_rule__match( | |
212 | git_attr_rule *rule, | |
213 | const git_attr_path *path) | |
214 | { | |
215 | int matched = git_attr_fnmatch__match(&rule->match, path); | |
ee1f0b1a | 216 | |
73b51450 | 217 | if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) |
ee1f0b1a RB |
218 | matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; |
219 | ||
220 | return matched; | |
221 | } | |
222 | ||
df743c7d | 223 | |
ee1f0b1a RB |
224 | git_attr_assignment *git_attr_rule__lookup_assignment( |
225 | git_attr_rule *rule, const char *name) | |
226 | { | |
227 | int pos; | |
228 | git_attr_name key; | |
229 | key.name = name; | |
230 | key.name_hash = git_attr_file__name_hash(name); | |
231 | ||
232 | pos = git_vector_bsearch(&rule->assigns, &key); | |
233 | git_clearerror(); /* okay if search failed */ | |
234 | ||
235 | return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; | |
236 | } | |
237 | ||
238 | int git_attr_path__init( | |
adc9bdb3 | 239 | git_attr_path *info, const char *path, const char *base) |
ee1f0b1a | 240 | { |
df743c7d | 241 | assert(info && path); |
ee1f0b1a RB |
242 | info->path = path; |
243 | info->basename = strrchr(path, '/'); | |
244 | if (info->basename) | |
245 | info->basename++; | |
246 | if (!info->basename || !*info->basename) | |
247 | info->basename = path; | |
adc9bdb3 RB |
248 | |
249 | if (base != NULL && git_path_root(path) < 0) { | |
250 | git_buf full_path = GIT_BUF_INIT; | |
251 | int error = git_buf_joinpath(&full_path, base, path); | |
252 | if (error == GIT_SUCCESS) | |
253 | info->is_dir = (git_path_isdir(full_path.ptr) == GIT_SUCCESS); | |
254 | git_buf_free(&full_path); | |
255 | return error; | |
256 | } | |
1744fafe | 257 | info->is_dir = (git_path_isdir(path) == GIT_SUCCESS); |
adc9bdb3 | 258 | |
ee1f0b1a RB |
259 | return GIT_SUCCESS; |
260 | } | |
261 | ||
262 | ||
263 | /* | |
264 | * From gitattributes(5): | |
265 | * | |
266 | * Patterns have the following format: | |
267 | * | |
268 | * - A blank line matches no files, so it can serve as a separator for | |
269 | * readability. | |
270 | * | |
271 | * - A line starting with # serves as a comment. | |
272 | * | |
273 | * - An optional prefix ! which negates the pattern; any matching file | |
274 | * excluded by a previous pattern will become included again. If a negated | |
275 | * pattern matches, this will override lower precedence patterns sources. | |
276 | * | |
277 | * - If the pattern ends with a slash, it is removed for the purpose of the | |
278 | * following description, but it would only find a match with a directory. In | |
279 | * other words, foo/ will match a directory foo and paths underneath it, but | |
280 | * will not match a regular file or a symbolic link foo (this is consistent | |
281 | * with the way how pathspec works in general in git). | |
282 | * | |
283 | * - If the pattern does not contain a slash /, git treats it as a shell glob | |
284 | * pattern and checks for a match against the pathname without leading | |
285 | * directories. | |
286 | * | |
287 | * - Otherwise, git treats the pattern as a shell glob suitable for consumption | |
288 | * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will | |
289 | * not match a / in the pathname. For example, "Documentation/\*.html" matches | |
290 | * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading | |
291 | * slash matches the beginning of the pathname; for example, "/\*.c" matches | |
292 | * "cat-file.c" but not "mozilla-sha1/sha1.c". | |
293 | */ | |
294 | ||
295 | /* | |
296 | * This will return GIT_SUCCESS if the spec was filled out, | |
297 | * GIT_ENOTFOUND if the fnmatch does not require matching, or | |
298 | * another error code there was an actual problem. | |
299 | */ | |
df743c7d | 300 | int git_attr_fnmatch__parse( |
ee1f0b1a | 301 | git_attr_fnmatch *spec, |
a51cd8e6 | 302 | const char *source, |
ee1f0b1a RB |
303 | const char **base) |
304 | { | |
df743c7d | 305 | const char *pattern, *scan; |
ee1f0b1a | 306 | int slash_count; |
ee1f0b1a | 307 | |
df743c7d | 308 | assert(spec && base && *base); |
ee1f0b1a RB |
309 | |
310 | pattern = *base; | |
311 | ||
312 | while (isspace(*pattern)) pattern++; | |
313 | if (!*pattern || *pattern == '#') { | |
df743c7d RB |
314 | *base = git__next_line(pattern); |
315 | return GIT_ENOTFOUND; | |
ee1f0b1a RB |
316 | } |
317 | ||
73b51450 RB |
318 | spec->flags = 0; |
319 | ||
320 | if (*pattern == '[') { | |
321 | if (strncmp(pattern, "[attr]", 6) == 0) { | |
322 | spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; | |
323 | pattern += 6; | |
73b51450 | 324 | } |
df743c7d | 325 | /* else a character range like [a-e]* which is accepted */ |
73b51450 RB |
326 | } |
327 | ||
ee1f0b1a | 328 | if (*pattern == '!') { |
73b51450 | 329 | spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; |
ee1f0b1a | 330 | pattern++; |
ee1f0b1a RB |
331 | } |
332 | ||
ee1f0b1a RB |
333 | slash_count = 0; |
334 | for (scan = pattern; *scan != '\0'; ++scan) { | |
df743c7d | 335 | /* scan until (non-escaped) white space */ |
ee1f0b1a RB |
336 | if (isspace(*scan) && *(scan - 1) != '\\') |
337 | break; | |
338 | ||
339 | if (*scan == '/') { | |
73b51450 | 340 | spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; |
ee1f0b1a RB |
341 | slash_count++; |
342 | } | |
343 | } | |
344 | ||
345 | *base = scan; | |
df743c7d | 346 | |
ee1f0b1a | 347 | spec->length = scan - pattern; |
ee1f0b1a RB |
348 | |
349 | if (pattern[spec->length - 1] == '/') { | |
350 | spec->length--; | |
73b51450 | 351 | spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; |
ee1f0b1a | 352 | if (--slash_count <= 0) |
73b51450 | 353 | spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; |
ee1f0b1a RB |
354 | } |
355 | ||
a51cd8e6 RB |
356 | if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 && |
357 | source != NULL && git_path_root(pattern) < 0) | |
358 | { | |
359 | size_t sourcelen = strlen(source); | |
360 | /* given an unrooted fullpath match from a file inside a repo, | |
361 | * prefix the pattern with the relative directory of the source file | |
362 | */ | |
363 | spec->pattern = git__malloc(sourcelen + spec->length + 1); | |
364 | if (spec->pattern) { | |
365 | memcpy(spec->pattern, source, sourcelen); | |
366 | memcpy(spec->pattern + sourcelen, pattern, spec->length); | |
367 | spec->length += sourcelen; | |
368 | spec->pattern[spec->length] = '\0'; | |
369 | } | |
370 | } else { | |
371 | spec->pattern = git__strndup(pattern, spec->length); | |
372 | } | |
373 | ||
374 | if (!spec->pattern) { | |
375 | *base = git__next_line(pattern); | |
376 | return GIT_ENOMEM; | |
377 | } else { | |
83bfbdf5 RB |
378 | /* strip '\' that might have be used for internal whitespace */ |
379 | char *to = spec->pattern; | |
380 | for (scan = spec->pattern; *scan; to++, scan++) { | |
381 | if (*scan == '\\') | |
382 | scan++; /* skip '\' but include next char */ | |
383 | if (to != scan) | |
384 | *to = *scan; | |
385 | } | |
386 | if (to != scan) { | |
387 | *to = '\0'; | |
388 | spec->length = (to - spec->pattern); | |
389 | } | |
a51cd8e6 RB |
390 | } |
391 | ||
ee1f0b1a | 392 | return GIT_SUCCESS; |
ee1f0b1a RB |
393 | } |
394 | ||
395 | static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) | |
396 | { | |
397 | const git_attr_name *a = a_raw; | |
398 | const git_attr_name *b = b_raw; | |
399 | ||
400 | if (b->name_hash < a->name_hash) | |
401 | return 1; | |
402 | else if (b->name_hash > a->name_hash) | |
403 | return -1; | |
404 | else | |
405 | return strcmp(b->name, a->name); | |
406 | } | |
407 | ||
bd370b14 | 408 | static void git_attr_assignment__free(git_attr_assignment *assign) |
73b51450 RB |
409 | { |
410 | git__free(assign->name); | |
411 | assign->name = NULL; | |
412 | ||
413 | if (assign->is_allocated) { | |
414 | git__free((void *)assign->value); | |
415 | assign->value = NULL; | |
416 | } | |
417 | ||
418 | git__free(assign); | |
419 | } | |
420 | ||
bd370b14 RB |
421 | static int merge_assignments(void **old_raw, void *new_raw) |
422 | { | |
423 | git_attr_assignment **old = (git_attr_assignment **)old_raw; | |
424 | git_attr_assignment *new = (git_attr_assignment *)new_raw; | |
425 | ||
426 | GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); | |
427 | *old = new; | |
428 | return GIT_EEXISTS; | |
429 | } | |
430 | ||
73b51450 RB |
431 | int git_attr_assignment__parse( |
432 | git_repository *repo, | |
ee1f0b1a RB |
433 | git_vector *assigns, |
434 | const char **base) | |
435 | { | |
436 | int error = GIT_SUCCESS; | |
437 | const char *scan = *base; | |
438 | git_attr_assignment *assign = NULL; | |
439 | ||
440 | assert(assigns && !assigns->length); | |
441 | ||
bd370b14 RB |
442 | assigns->_cmp = sort_by_hash_and_name; |
443 | ||
73b51450 | 444 | while (*scan && *scan != '\n' && error == GIT_SUCCESS) { |
ee1f0b1a RB |
445 | const char *name_start, *value_start; |
446 | ||
447 | /* skip leading blanks */ | |
448 | while (isspace(*scan) && *scan != '\n') scan++; | |
449 | ||
450 | /* allocate assign if needed */ | |
451 | if (!assign) { | |
452 | assign = git__calloc(1, sizeof(git_attr_assignment)); | |
453 | if (!assign) { | |
454 | error = GIT_ENOMEM; | |
455 | break; | |
456 | } | |
bd370b14 | 457 | GIT_REFCOUNT_INC(assign); |
ee1f0b1a RB |
458 | } |
459 | ||
460 | assign->name_hash = 5381; | |
461 | assign->value = GIT_ATTR_TRUE; | |
462 | assign->is_allocated = 0; | |
463 | ||
464 | /* look for magic name prefixes */ | |
465 | if (*scan == '-') { | |
466 | assign->value = GIT_ATTR_FALSE; | |
467 | scan++; | |
468 | } else if (*scan == '!') { | |
469 | assign->value = NULL; /* explicit unspecified state */ | |
470 | scan++; | |
471 | } else if (*scan == '#') /* comment rest of line */ | |
472 | break; | |
473 | ||
474 | /* find the name */ | |
475 | name_start = scan; | |
476 | while (*scan && !isspace(*scan) && *scan != '=') { | |
477 | assign->name_hash = | |
478 | ((assign->name_hash << 5) + assign->name_hash) + *scan; | |
479 | scan++; | |
480 | } | |
73b51450 | 481 | if (scan == name_start) { |
ee1f0b1a RB |
482 | /* must have found lone prefix (" - ") or leading = ("=foo") |
483 | * or end of buffer -- advance until whitespace and continue | |
484 | */ | |
485 | while (*scan && !isspace(*scan)) scan++; | |
486 | continue; | |
487 | } | |
488 | ||
73b51450 RB |
489 | /* allocate permanent storage for name */ |
490 | assign->name = git__strndup(name_start, scan - name_start); | |
491 | if (!assign->name) { | |
492 | error = GIT_ENOMEM; | |
493 | break; | |
494 | } | |
495 | ||
ee1f0b1a RB |
496 | /* if there is an equals sign, find the value */ |
497 | if (*scan == '=') { | |
498 | for (value_start = ++scan; *scan && !isspace(*scan); ++scan); | |
499 | ||
500 | /* if we found a value, allocate permanent storage for it */ | |
501 | if (scan > value_start) { | |
502 | assign->value = git__strndup(value_start, scan - value_start); | |
503 | if (!assign->value) { | |
504 | error = GIT_ENOMEM; | |
505 | break; | |
506 | } else { | |
507 | assign->is_allocated = 1; | |
508 | } | |
509 | } | |
510 | } | |
511 | ||
bd370b14 RB |
512 | /* expand macros (if given a repo with a macro cache) */ |
513 | if (repo != NULL && assign->value == GIT_ATTR_TRUE) { | |
73b51450 RB |
514 | git_attr_rule *macro = |
515 | git_hashtable_lookup(repo->attrcache.macros, assign->name); | |
516 | ||
517 | if (macro != NULL) { | |
518 | unsigned int i; | |
519 | git_attr_assignment *massign; | |
520 | ||
bd370b14 RB |
521 | git_vector_foreach(¯o->assigns, i, massign) { |
522 | GIT_REFCOUNT_INC(massign); | |
73b51450 | 523 | |
bd370b14 RB |
524 | error = git_vector_insert_sorted( |
525 | assigns, massign, &merge_assignments); | |
73b51450 | 526 | |
bd370b14 RB |
527 | if (error == GIT_EEXISTS) |
528 | error = GIT_SUCCESS; | |
529 | else if (error != GIT_SUCCESS) | |
73b51450 | 530 | break; |
73b51450 | 531 | } |
73b51450 | 532 | } |
ee1f0b1a RB |
533 | } |
534 | ||
535 | /* insert allocated assign into vector */ | |
bd370b14 RB |
536 | error = git_vector_insert_sorted(assigns, assign, &merge_assignments); |
537 | if (error == GIT_EEXISTS) | |
538 | error = GIT_SUCCESS; | |
539 | else if (error < GIT_SUCCESS) | |
ee1f0b1a RB |
540 | break; |
541 | ||
542 | /* clear assign since it is now "owned" by the vector */ | |
543 | assign = NULL; | |
544 | } | |
545 | ||
546 | if (!assigns->length) | |
547 | error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule"); | |
ee1f0b1a | 548 | |
73b51450 | 549 | if (assign != NULL) |
bd370b14 | 550 | git_attr_assignment__free(assign); |
ee1f0b1a | 551 | |
df743c7d | 552 | *base = git__next_line(scan); |
ee1f0b1a RB |
553 | |
554 | return error; | |
555 | } | |
556 | ||
c6d2a2c0 | 557 | static void git_attr_rule__clear(git_attr_rule *rule) |
ee1f0b1a RB |
558 | { |
559 | unsigned int i; | |
560 | git_attr_assignment *assign; | |
561 | ||
562 | if (!rule) | |
73b51450 | 563 | return; |
ee1f0b1a | 564 | |
df743c7d RB |
565 | if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { |
566 | git_vector_foreach(&rule->assigns, i, assign) | |
567 | GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); | |
568 | git_vector_free(&rule->assigns); | |
569 | } | |
570 | ||
ee1f0b1a RB |
571 | git__free(rule->match.pattern); |
572 | rule->match.pattern = NULL; | |
573 | rule->match.length = 0; | |
ee1f0b1a | 574 | } |
c6d2a2c0 RB |
575 | |
576 | void git_attr_rule__free(git_attr_rule *rule) | |
577 | { | |
578 | git_attr_rule__clear(rule); | |
579 | git__free(rule); | |
580 | } | |
581 |