]> git.proxmox.com Git - libgit2.git/blob - src/attr_file.c
Merge pull request #2725 from libgit2/vmg/attr-null
[libgit2.git] / src / attr_file.c
1 #include "common.h"
2 #include "repository.h"
3 #include "filebuf.h"
4 #include "attr_file.h"
5 #include "attrcache.h"
6 #include "git2/blob.h"
7 #include "git2/tree.h"
8 #include "index.h"
9 #include <ctype.h>
10
11 static void attr_file_free(git_attr_file *file)
12 {
13 bool unlock = !git_mutex_lock(&file->lock);
14 git_attr_file__clear_rules(file, false);
15 git_pool_clear(&file->pool);
16 if (unlock)
17 git_mutex_unlock(&file->lock);
18 git_mutex_free(&file->lock);
19
20 git__memzero(file, sizeof(*file));
21 git__free(file);
22 }
23
24 int git_attr_file__new(
25 git_attr_file **out,
26 git_attr_file_entry *entry,
27 git_attr_file_source source)
28 {
29 git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
30 GITERR_CHECK_ALLOC(attrs);
31
32 if (git_mutex_init(&attrs->lock) < 0) {
33 giterr_set(GITERR_OS, "Failed to initialize lock");
34 git__free(attrs);
35 return -1;
36 }
37
38 if (git_pool_init(&attrs->pool, 1, 0) < 0) {
39 attr_file_free(attrs);
40 return -1;
41 }
42
43 GIT_REFCOUNT_INC(attrs);
44 attrs->entry = entry;
45 attrs->source = source;
46 *out = attrs;
47 return 0;
48 }
49
50 int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
51 {
52 unsigned int i;
53 git_attr_rule *rule;
54
55 if (need_lock && git_mutex_lock(&file->lock) < 0) {
56 giterr_set(GITERR_OS, "Failed to lock attribute file");
57 return -1;
58 }
59
60 git_vector_foreach(&file->rules, i, rule)
61 git_attr_rule__free(rule);
62 git_vector_free(&file->rules);
63
64 if (need_lock)
65 git_mutex_unlock(&file->lock);
66
67 return 0;
68 }
69
70 void git_attr_file__free(git_attr_file *file)
71 {
72 if (!file)
73 return;
74 GIT_REFCOUNT_DEC(file, attr_file_free);
75 }
76
77 static int attr_file_oid_from_index(
78 git_oid *oid, git_repository *repo, const char *path)
79 {
80 int error;
81 git_index *idx;
82 size_t pos;
83 const git_index_entry *entry;
84
85 if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
86 (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
87 return error;
88
89 if (!(entry = git_index_get_byindex(idx, pos)))
90 return GIT_ENOTFOUND;
91
92 *oid = entry->id;
93 return 0;
94 }
95
96 int git_attr_file__load(
97 git_attr_file **out,
98 git_repository *repo,
99 git_attr_file_entry *entry,
100 git_attr_file_source source,
101 git_attr_file_parser parser)
102 {
103 int error = 0;
104 git_blob *blob = NULL;
105 git_buf content = GIT_BUF_INIT;
106 git_attr_file *file;
107 struct stat st;
108
109 *out = NULL;
110
111 switch (source) {
112 case GIT_ATTR_FILE__IN_MEMORY:
113 /* in-memory attribute file doesn't need data */
114 break;
115 case GIT_ATTR_FILE__FROM_INDEX: {
116 git_oid id;
117
118 if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
119 (error = git_blob_lookup(&blob, repo, &id)) < 0)
120 return error;
121
122 /* Do not assume that data straight from the ODB is NULL-terminated;
123 * copy the contents of a file to a buffer to work on */
124 git_buf_put(&content, git_blob_rawcontent(blob), git_blob_rawsize(blob));
125 break;
126 }
127 case GIT_ATTR_FILE__FROM_FILE: {
128 int fd;
129
130 if (p_stat(entry->fullpath, &st) < 0)
131 return git_path_set_error(errno, entry->fullpath, "stat");
132 if (S_ISDIR(st.st_mode))
133 return GIT_ENOTFOUND;
134
135 /* For open or read errors, return ENOTFOUND to skip item */
136 /* TODO: issue warning when warning API is available */
137
138 if ((fd = git_futils_open_ro(entry->fullpath)) < 0)
139 return GIT_ENOTFOUND;
140
141 error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size);
142 p_close(fd);
143
144 if (error < 0)
145 return GIT_ENOTFOUND;
146
147 break;
148 }
149 default:
150 giterr_set(GITERR_INVALID, "Unknown file source %d", source);
151 return -1;
152 }
153
154 if ((error = git_attr_file__new(&file, entry, source)) < 0)
155 goto cleanup;
156
157 if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
158 git_attr_file__free(file);
159 goto cleanup;
160 }
161
162 /* write cache breaker */
163 if (source == GIT_ATTR_FILE__FROM_INDEX)
164 git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
165 else if (source == GIT_ATTR_FILE__FROM_FILE)
166 git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
167 /* else always cacheable */
168
169 *out = file;
170
171 cleanup:
172 git_blob_free(blob);
173 git_buf_free(&content);
174
175 return error;
176 }
177
178 int git_attr_file__out_of_date(git_repository *repo, git_attr_file *file)
179 {
180 if (!file)
181 return 1;
182
183 switch (file->source) {
184 case GIT_ATTR_FILE__IN_MEMORY:
185 return 0;
186
187 case GIT_ATTR_FILE__FROM_FILE:
188 return git_futils_filestamp_check(
189 &file->cache_data.stamp, file->entry->fullpath);
190
191 case GIT_ATTR_FILE__FROM_INDEX: {
192 int error;
193 git_oid id;
194
195 if ((error = attr_file_oid_from_index(
196 &id, repo, file->entry->path)) < 0)
197 return error;
198
199 return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
200 }
201
202 default:
203 giterr_set(GITERR_INVALID, "Invalid file type %d", file->source);
204 return -1;
205 }
206 }
207
208 static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
209 static void git_attr_rule__clear(git_attr_rule *rule);
210 static bool parse_optimized_patterns(
211 git_attr_fnmatch *spec,
212 git_pool *pool,
213 const char *pattern);
214
215 int git_attr_file__parse_buffer(
216 git_repository *repo, git_attr_file *attrs, const char *data)
217 {
218 int error = 0;
219 const char *scan = data, *context = NULL;
220 git_attr_rule *rule = NULL;
221
222 /* if subdir file path, convert context for file paths */
223 if (attrs->entry &&
224 git_path_root(attrs->entry->path) < 0 &&
225 !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
226 context = attrs->entry->path;
227
228 if (git_mutex_lock(&attrs->lock) < 0) {
229 giterr_set(GITERR_OS, "Failed to lock attribute file");
230 return -1;
231 }
232
233 while (!error && *scan) {
234 /* allocate rule if needed */
235 if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
236 error = -1;
237 break;
238 }
239
240 rule->match.flags =
241 GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
242
243 /* parse the next "pattern attr attr attr" line */
244 if (!(error = git_attr_fnmatch__parse(
245 &rule->match, &attrs->pool, context, &scan)) &&
246 !(error = git_attr_assignment__parse(
247 repo, &attrs->pool, &rule->assigns, &scan)))
248 {
249 if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
250 /* TODO: warning if macro found in file below repo root */
251 error = git_attr_cache__insert_macro(repo, rule);
252 else
253 error = git_vector_insert(&attrs->rules, rule);
254 }
255
256 /* if the rule wasn't a pattern, on to the next */
257 if (error < 0) {
258 git_attr_rule__clear(rule); /* reset rule contents */
259 if (error == GIT_ENOTFOUND)
260 error = 0;
261 } else {
262 rule = NULL; /* vector now "owns" the rule */
263 }
264 }
265
266 git_mutex_unlock(&attrs->lock);
267 git_attr_rule__free(rule);
268
269 return error;
270 }
271
272 uint32_t git_attr_file__name_hash(const char *name)
273 {
274 uint32_t h = 5381;
275 int c;
276 assert(name);
277 while ((c = (int)*name++) != 0)
278 h = ((h << 5) + h) + c;
279 return h;
280 }
281
282 int git_attr_file__lookup_one(
283 git_attr_file *file,
284 git_attr_path *path,
285 const char *attr,
286 const char **value)
287 {
288 size_t i;
289 git_attr_name name;
290 git_attr_rule *rule;
291
292 *value = NULL;
293
294 name.name = attr;
295 name.name_hash = git_attr_file__name_hash(attr);
296
297 git_attr_file__foreach_matching_rule(file, path, i, rule) {
298 size_t pos;
299
300 if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
301 *value = ((git_attr_assignment *)
302 git_vector_get(&rule->assigns, pos))->value;
303 break;
304 }
305 }
306
307 return 0;
308 }
309
310 int git_attr_file__load_standalone(git_attr_file **out, const char *path)
311 {
312 int error;
313 git_attr_file *file;
314 git_buf content = GIT_BUF_INIT;
315
316 error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
317 if (error < 0)
318 return error;
319
320 error = git_attr_cache__alloc_file_entry(
321 &file->entry, NULL, path, &file->pool);
322 if (error < 0) {
323 git_attr_file__free(file);
324 return error;
325 }
326 /* because the cache entry is allocated from the file's own pool, we
327 * don't have to free it - freeing file+pool will free cache entry, too.
328 */
329
330 if (!(error = git_futils_readbuffer(&content, path))) {
331 error = git_attr_file__parse_buffer(NULL, file, content.ptr);
332 git_buf_free(&content);
333 }
334
335 if (error < 0)
336 git_attr_file__free(file);
337 else
338 *out = file;
339
340 return error;
341 }
342
343 bool git_attr_fnmatch__match(
344 git_attr_fnmatch *match,
345 git_attr_path *path)
346 {
347 const char *filename;
348 int flags = 0;
349
350 /*
351 * If the rule was generated in a subdirectory, we must only
352 * use it for paths inside that directory. We can thus return
353 * a non-match if the prefixes don't match.
354 */
355 if (match->containing_dir) {
356 if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
357 if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
358 return 0;
359 } else {
360 if (git__prefixcmp(path->path, match->containing_dir))
361 return 0;
362 }
363 }
364
365 if (match->flags & GIT_ATTR_FNMATCH_ICASE)
366 flags |= FNM_CASEFOLD;
367 if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
368 flags |= FNM_LEADING_DIR;
369
370 if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
371 filename = path->path;
372 flags |= FNM_PATHNAME;
373 } else {
374 filename = path->basename;
375
376 if (path->is_dir)
377 flags |= FNM_LEADING_DIR;
378 }
379
380 if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
381 int matchval;
382
383 /* for attribute checks or root ignore checks, fail match */
384 if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
385 path->basename == path->path)
386 return false;
387
388 /* for ignore checks, use container of current item for check */
389 path->basename[-1] = '\0';
390 flags |= FNM_LEADING_DIR;
391 matchval = p_fnmatch(match->pattern, path->path, flags);
392 path->basename[-1] = '/';
393 return (matchval != FNM_NOMATCH);
394 }
395
396 /* if path is a directory prefix of a negated pattern, then match */
397 if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
398 size_t pathlen = strlen(path->path);
399 bool prefixed = (pathlen <= match->length) &&
400 ((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
401 !strncasecmp(match->pattern, path->path, pathlen) :
402 !strncmp(match->pattern, path->path, pathlen));
403
404 if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
405 return true;
406 }
407
408 return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
409 }
410
411 bool git_attr_rule__match(
412 git_attr_rule *rule,
413 git_attr_path *path)
414 {
415 bool matched = git_attr_fnmatch__match(&rule->match, path);
416
417 if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
418 matched = !matched;
419
420 return matched;
421 }
422
423 git_attr_assignment *git_attr_rule__lookup_assignment(
424 git_attr_rule *rule, const char *name)
425 {
426 size_t pos;
427 git_attr_name key;
428 key.name = name;
429 key.name_hash = git_attr_file__name_hash(name);
430
431 if (git_vector_bsearch(&pos, &rule->assigns, &key))
432 return NULL;
433
434 return git_vector_get(&rule->assigns, pos);
435 }
436
437 int git_attr_path__init(
438 git_attr_path *info, const char *path, const char *base)
439 {
440 ssize_t root;
441
442 /* build full path as best we can */
443 git_buf_init(&info->full, 0);
444
445 if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
446 return -1;
447
448 info->path = info->full.ptr + root;
449
450 /* remove trailing slashes */
451 while (info->full.size > 0) {
452 if (info->full.ptr[info->full.size - 1] != '/')
453 break;
454 info->full.size--;
455 }
456 info->full.ptr[info->full.size] = '\0';
457
458 /* skip leading slashes in path */
459 while (*info->path == '/')
460 info->path++;
461
462 /* find trailing basename component */
463 info->basename = strrchr(info->path, '/');
464 if (info->basename)
465 info->basename++;
466 if (!info->basename || !*info->basename)
467 info->basename = info->path;
468
469 info->is_dir = (int)git_path_isdir(info->full.ptr);
470
471 return 0;
472 }
473
474 void git_attr_path__free(git_attr_path *info)
475 {
476 git_buf_free(&info->full);
477 info->path = NULL;
478 info->basename = NULL;
479 }
480
481 /*
482 * From gitattributes(5):
483 *
484 * Patterns have the following format:
485 *
486 * - A blank line matches no files, so it can serve as a separator for
487 * readability.
488 *
489 * - A line starting with # serves as a comment.
490 *
491 * - An optional prefix ! which negates the pattern; any matching file
492 * excluded by a previous pattern will become included again. If a negated
493 * pattern matches, this will override lower precedence patterns sources.
494 *
495 * - If the pattern ends with a slash, it is removed for the purpose of the
496 * following description, but it would only find a match with a directory. In
497 * other words, foo/ will match a directory foo and paths underneath it, but
498 * will not match a regular file or a symbolic link foo (this is consistent
499 * with the way how pathspec works in general in git).
500 *
501 * - If the pattern does not contain a slash /, git treats it as a shell glob
502 * pattern and checks for a match against the pathname without leading
503 * directories.
504 *
505 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
506 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
507 * not match a / in the pathname. For example, "Documentation/\*.html" matches
508 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
509 * slash matches the beginning of the pathname; for example, "/\*.c" matches
510 * "cat-file.c" but not "mozilla-sha1/sha1.c".
511 */
512
513 /*
514 * This will return 0 if the spec was filled out,
515 * GIT_ENOTFOUND if the fnmatch does not require matching, or
516 * another error code there was an actual problem.
517 */
518 int git_attr_fnmatch__parse(
519 git_attr_fnmatch *spec,
520 git_pool *pool,
521 const char *context,
522 const char **base)
523 {
524 const char *pattern, *scan;
525 int slash_count, allow_space;
526
527 assert(spec && base && *base);
528
529 if (parse_optimized_patterns(spec, pool, *base))
530 return 0;
531
532 spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
533 allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
534
535 pattern = *base;
536
537 while (git__isspace(*pattern)) pattern++;
538 if (!*pattern || *pattern == '#') {
539 *base = git__next_line(pattern);
540 return GIT_ENOTFOUND;
541 }
542
543 if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
544 if (strncmp(pattern, "[attr]", 6) == 0) {
545 spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
546 pattern += 6;
547 }
548 /* else a character range like [a-e]* which is accepted */
549 }
550
551 if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
552 spec->flags = spec->flags |
553 GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
554 pattern++;
555 }
556
557 slash_count = 0;
558 for (scan = pattern; *scan != '\0'; ++scan) {
559 /* scan until (non-escaped) white space */
560 if (git__isspace(*scan) && *(scan - 1) != '\\') {
561 if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r'))
562 break;
563 }
564
565 if (*scan == '/') {
566 spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
567 slash_count++;
568 if (pattern == scan)
569 pattern++;
570 }
571 /* remember if we see an unescaped wildcard in pattern */
572 else if (git__iswildcard(*scan) &&
573 (scan == pattern || (*(scan - 1) != '\\')))
574 spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
575 }
576
577 *base = scan;
578
579 if ((spec->length = scan - pattern) == 0)
580 return GIT_ENOTFOUND;
581
582 /*
583 * Remove one trailing \r in case this is a CRLF delimited
584 * file, in the case of Icon\r\r\n, we still leave the first
585 * \r there to match against.
586 */
587 if (pattern[spec->length - 1] == '\r')
588 if (--spec->length == 0)
589 return GIT_ENOTFOUND;
590
591 if (pattern[spec->length - 1] == '/') {
592 spec->length--;
593 spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
594 if (--slash_count <= 0)
595 spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
596 }
597 if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
598 spec->length >= 2 &&
599 pattern[spec->length - 1] == '*' &&
600 pattern[spec->length - 2] == '/') {
601 spec->length -= 2;
602 spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
603 /* leave FULLPATH match on, however */
604 }
605
606 if (context) {
607 char *slash = strchr(context, '/');
608 size_t len;
609 if (slash) {
610 /* include the slash for easier matching */
611 len = slash - context + 1;
612 spec->containing_dir = git_pool_strndup(pool, context, len);
613 spec->containing_dir_length = len;
614 }
615 }
616
617 if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
618 context != NULL && git_path_root(pattern) < 0)
619 {
620 /* use context path minus the trailing filename */
621 char *slash = strrchr(context, '/');
622 size_t contextlen = slash ? slash - context + 1 : 0;
623
624 /* given an unrooted fullpath match from a file inside a repo,
625 * prefix the pattern with the relative directory of the source file
626 */
627 spec->pattern = git_pool_malloc(
628 pool, (uint32_t)(contextlen + spec->length + 1));
629 if (spec->pattern) {
630 memcpy(spec->pattern, context, contextlen);
631 memcpy(spec->pattern + contextlen, pattern, spec->length);
632 spec->length += contextlen;
633 spec->pattern[spec->length] = '\0';
634 }
635 } else {
636 spec->pattern = git_pool_strndup(pool, pattern, spec->length);
637 }
638
639 if (!spec->pattern) {
640 *base = git__next_line(pattern);
641 return -1;
642 } else {
643 /* strip '\' that might have be used for internal whitespace */
644 spec->length = git__unescape(spec->pattern);
645 /* TODO: convert remaining '\' into '/' for POSIX ??? */
646 }
647
648 return 0;
649 }
650
651 static bool parse_optimized_patterns(
652 git_attr_fnmatch *spec,
653 git_pool *pool,
654 const char *pattern)
655 {
656 if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
657 spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
658 spec->pattern = git_pool_strndup(pool, pattern, 1);
659 spec->length = 1;
660
661 return true;
662 }
663
664 return false;
665 }
666
667 static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
668 {
669 const git_attr_name *a = a_raw;
670 const git_attr_name *b = b_raw;
671
672 if (b->name_hash < a->name_hash)
673 return 1;
674 else if (b->name_hash > a->name_hash)
675 return -1;
676 else
677 return strcmp(b->name, a->name);
678 }
679
680 static void git_attr_assignment__free(git_attr_assignment *assign)
681 {
682 /* name and value are stored in a git_pool associated with the
683 * git_attr_file, so they do not need to be freed here
684 */
685 assign->name = NULL;
686 assign->value = NULL;
687 git__free(assign);
688 }
689
690 static int merge_assignments(void **old_raw, void *new_raw)
691 {
692 git_attr_assignment **old = (git_attr_assignment **)old_raw;
693 git_attr_assignment *new = (git_attr_assignment *)new_raw;
694
695 GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
696 *old = new;
697 return GIT_EEXISTS;
698 }
699
700 int git_attr_assignment__parse(
701 git_repository *repo,
702 git_pool *pool,
703 git_vector *assigns,
704 const char **base)
705 {
706 int error;
707 const char *scan = *base;
708 git_attr_assignment *assign = NULL;
709
710 assert(assigns && !assigns->length);
711
712 git_vector_set_cmp(assigns, sort_by_hash_and_name);
713
714 while (*scan && *scan != '\n') {
715 const char *name_start, *value_start;
716
717 /* skip leading blanks */
718 while (git__isspace(*scan) && *scan != '\n') scan++;
719
720 /* allocate assign if needed */
721 if (!assign) {
722 assign = git__calloc(1, sizeof(git_attr_assignment));
723 GITERR_CHECK_ALLOC(assign);
724 GIT_REFCOUNT_INC(assign);
725 }
726
727 assign->name_hash = 5381;
728 assign->value = git_attr__true;
729
730 /* look for magic name prefixes */
731 if (*scan == '-') {
732 assign->value = git_attr__false;
733 scan++;
734 } else if (*scan == '!') {
735 assign->value = git_attr__unset; /* explicit unspecified state */
736 scan++;
737 } else if (*scan == '#') /* comment rest of line */
738 break;
739
740 /* find the name */
741 name_start = scan;
742 while (*scan && !git__isspace(*scan) && *scan != '=') {
743 assign->name_hash =
744 ((assign->name_hash << 5) + assign->name_hash) + *scan;
745 scan++;
746 }
747 if (scan == name_start) {
748 /* must have found lone prefix (" - ") or leading = ("=foo")
749 * or end of buffer -- advance until whitespace and continue
750 */
751 while (*scan && !git__isspace(*scan)) scan++;
752 continue;
753 }
754
755 /* allocate permanent storage for name */
756 assign->name = git_pool_strndup(pool, name_start, scan - name_start);
757 GITERR_CHECK_ALLOC(assign->name);
758
759 /* if there is an equals sign, find the value */
760 if (*scan == '=') {
761 for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
762
763 /* if we found a value, allocate permanent storage for it */
764 if (scan > value_start) {
765 assign->value = git_pool_strndup(pool, value_start, scan - value_start);
766 GITERR_CHECK_ALLOC(assign->value);
767 }
768 }
769
770 /* expand macros (if given a repo with a macro cache) */
771 if (repo != NULL && assign->value == git_attr__true) {
772 git_attr_rule *macro =
773 git_attr_cache__lookup_macro(repo, assign->name);
774
775 if (macro != NULL) {
776 unsigned int i;
777 git_attr_assignment *massign;
778
779 git_vector_foreach(&macro->assigns, i, massign) {
780 GIT_REFCOUNT_INC(massign);
781
782 error = git_vector_insert_sorted(
783 assigns, massign, &merge_assignments);
784 if (error < 0 && error != GIT_EEXISTS)
785 return error;
786 }
787 }
788 }
789
790 /* insert allocated assign into vector */
791 error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
792 if (error < 0 && error != GIT_EEXISTS)
793 return error;
794
795 /* clear assign since it is now "owned" by the vector */
796 assign = NULL;
797 }
798
799 if (assign != NULL)
800 git_attr_assignment__free(assign);
801
802 *base = git__next_line(scan);
803
804 return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
805 }
806
807 static void git_attr_rule__clear(git_attr_rule *rule)
808 {
809 unsigned int i;
810 git_attr_assignment *assign;
811
812 if (!rule)
813 return;
814
815 if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
816 git_vector_foreach(&rule->assigns, i, assign)
817 GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
818 git_vector_free(&rule->assigns);
819 }
820
821 /* match.pattern is stored in a git_pool, so no need to free */
822 rule->match.pattern = NULL;
823 rule->match.length = 0;
824 }
825
826 void git_attr_rule__free(git_attr_rule *rule)
827 {
828 git_attr_rule__clear(rule);
829 git__free(rule);
830 }
831