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