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