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