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