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