File Coverage

deps/libgit2/src/attr_file.c
Criterion Covered Total %
statement 263 445 59.1
branch 148 364 40.6
condition n/a
subroutine n/a
pod n/a
total 411 809 50.8


line stmt bran cond sub pod time code
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
20              
21 2158           static void attr_file_free(git_attr_file *file)
22             {
23 2158           bool unlock = !git_mutex_lock(&file->lock);
24 2158           git_attr_file__clear_rules(file, false);
25 2158           git_pool_clear(&file->pool);
26             if (unlock)
27             git_mutex_unlock(&file->lock);
28             git_mutex_free(&file->lock);
29              
30 2158           git__memzero(file, sizeof(*file));
31 2158           git__free(file);
32 2158           }
33              
34 2158           int git_attr_file__new(
35             git_attr_file **out,
36             git_attr_file_entry *entry,
37             git_attr_file_source source)
38             {
39 2158           git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
40 2158 50         GIT_ERROR_CHECK_ALLOC(attrs);
41              
42 2158 50         if (git_mutex_init(&attrs->lock) < 0) {
43 0           git_error_set(GIT_ERROR_OS, "failed to initialize lock");
44 0           git__free(attrs);
45 0           return -1;
46             }
47              
48 2158           git_pool_init(&attrs->pool, 1);
49 2158           GIT_REFCOUNT_INC(attrs);
50 2158           attrs->entry = entry;
51 2158           attrs->source = source;
52 2158           *out = attrs;
53 2158           return 0;
54             }
55              
56 2158           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 2158 50         if (need_lock && git_mutex_lock(&file->lock) < 0) {
    0          
62 0           git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
63 0           return -1;
64             }
65              
66 2202 100         git_vector_foreach(&file->rules, i, rule)
67 44           git_attr_rule__free(rule);
68 2158           git_vector_free(&file->rules);
69              
70             if (need_lock)
71             git_mutex_unlock(&file->lock);
72              
73 2158           return 0;
74             }
75              
76 7683           void git_attr_file__free(git_attr_file *file)
77             {
78 7683 100         if (!file)
79 584           return;
80 7099 100         GIT_REFCOUNT_DEC(file, attr_file_free);
    50          
81             }
82              
83 1132           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 1132 50         if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
    50          
92 1132           (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
93 1132           return error;
94              
95 0 0         if (!(entry = git_index_get_byindex(idx, pos)))
96 0           return GIT_ENOTFOUND;
97              
98 0           *oid = entry->id;
99 1132           return 0;
100             }
101              
102 3290           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 3290           int error = 0;
112 3290           git_tree *tree = NULL;
113 3290           git_tree_entry *tree_entry = NULL;
114 3290           git_blob *blob = NULL;
115 3290           git_buf content = GIT_BUF_INIT;
116             const char *content_str;
117             git_attr_file *file;
118             struct stat st;
119 3290           bool nonexistent = false;
120             int bom_offset;
121             git_bom_t bom;
122             git_oid id;
123             git_object_size_t blobsize;
124              
125 3290           *out = NULL;
126              
127 3290           switch (source) {
128             case GIT_ATTR_FILE__IN_MEMORY:
129             /* in-memory attribute file doesn't need data */
130 14           break;
131             case GIT_ATTR_FILE__FROM_INDEX: {
132 1132 50         if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
    0          
133             (error = git_blob_lookup(&blob, repo, &id)) < 0)
134 1132           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 0           blobsize = git_blob_rawsize(blob);
139              
140 0 0         GIT_ERROR_CHECK_BLOBSIZE(blobsize);
141 0           git_buf_put(&content, git_blob_rawcontent(blob), (size_t)blobsize);
142 0           break;
143             }
144             case GIT_ATTR_FILE__FROM_FILE: {
145 2144           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 2144 100         if (p_stat(entry->fullpath, &st) < 0 ||
    50          
151 15 50         S_ISDIR(st.st_mode) ||
152 15 50         (fd = git_futils_open_ro(entry->fullpath)) < 0 ||
153 15           (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
154 2129           nonexistent = true;
155              
156 2144 100         if (fd >= 0)
157 15           p_close(fd);
158              
159 2144           break;
160             }
161             case GIT_ATTR_FILE__FROM_HEAD: {
162 0 0         if ((error = git_repository_head_tree(&tree, repo)) < 0 ||
    0          
163 0 0         (error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0 ||
164 0           (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 0           blobsize = git_blob_rawsize(blob);
172              
173 0 0         GIT_ERROR_CHECK_BLOBSIZE(blobsize);
174 0 0         if ((error = git_buf_put(&content,
175 0           git_blob_rawcontent(blob), (size_t)blobsize)) < 0)
176 0           goto cleanup;
177              
178 0           break;
179             }
180             default:
181 0           git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source);
182 0           return -1;
183             }
184              
185 2158 50         if ((error = git_attr_file__new(&file, entry, source)) < 0)
186 0           goto cleanup;
187              
188             /* advance over a UTF8 BOM */
189 2158           content_str = git_buf_cstr(&content);
190 2158           bom_offset = git_buf_text_detect_bom(&bom, &content);
191              
192 2158 50         if (bom == GIT_BOM_UTF8)
193 0           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 2158 100         if (attr_session)
199 117           file->session_key = attr_session->key;
200              
201 2158 100         if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) {
    50          
202 0           git_attr_file__free(file);
203 0           goto cleanup;
204             }
205              
206             /* write cache breakers */
207 2158 100         if (nonexistent)
208 2129           file->nonexistent = 1;
209 29 50         else if (source == GIT_ATTR_FILE__FROM_INDEX)
210 0           git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
211 29 50         else if (source == GIT_ATTR_FILE__FROM_HEAD)
212 0           git_oid_cpy(&file->cache_data.oid, git_tree_id(tree));
213 29 100         else if (source == GIT_ATTR_FILE__FROM_FILE)
214 15           git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
215             /* else always cacheable */
216              
217 2158           *out = file;
218              
219             cleanup:
220 2158           git_blob_free(blob);
221 2158           git_tree_entry_free(tree_entry);
222 2158           git_tree_free(tree);
223 2158           git_buf_dispose(&content);
224              
225 3290           return error;
226             }
227              
228 2783           int git_attr_file__out_of_date(
229             git_repository *repo,
230             git_attr_session *attr_session,
231             git_attr_file *file)
232             {
233 2783 50         if (!file)
234 0           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 2783 100         if (attr_session && attr_session->key == file->session_key)
    100          
240 361           return 0;
241 2422 100         else if (file->nonexistent)
242 2057           return 1;
243              
244 365           switch (file->source) {
245             case GIT_ATTR_FILE__IN_MEMORY:
246 174           return 0;
247              
248             case GIT_ATTR_FILE__FROM_FILE:
249 191           return git_futils_filestamp_check(
250 191           &file->cache_data.stamp, file->entry->fullpath);
251              
252             case GIT_ATTR_FILE__FROM_INDEX: {
253             int error;
254             git_oid id;
255              
256 0 0         if ((error = attr_file_oid_from_index(
257 0           &id, repo, file->entry->path)) < 0)
258 0           return error;
259              
260 0           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 0 0         if ((error = git_repository_head_tree(&tree, repo)) < 0)
268 0           return error;
269              
270 0           error = git_oid__cmp(&file->cache_data.oid, git_tree_id(tree));
271              
272 0           git_tree_free(tree);
273 0           return error;
274             }
275              
276             default:
277 0           git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source);
278 0           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 1787           int git_attr_file__parse_buffer(
290             git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
291             {
292 1787           const char *scan = data, *context = NULL;
293 1787           git_attr_rule *rule = NULL;
294 1787           int error = 0;
295              
296             /* If subdir file path, convert context for file paths */
297 3572 50         if (attrs->entry && git_path_root(attrs->entry->path) < 0 &&
298 1785           !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
299 87           context = attrs->entry->path;
300              
301 1787 50         if (git_mutex_lock(&attrs->lock) < 0) {
302 0           git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
303 0           return -1;
304             }
305              
306 1787 50         while (!error && *scan) {
    50          
307             /* Allocate rule if needed, otherwise re-use previous rule */
308 0 0         if (!rule) {
309 0           rule = git__calloc(1, sizeof(*rule));
310 0 0         GIT_ERROR_CHECK_ALLOC(rule);
311             } else
312 0           git_attr_rule__clear(rule);
313              
314 0           rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
315              
316             /* Parse the next "pattern attr attr attr" line */
317 0 0         if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 ||
    0          
318 0           (error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0)
319             {
320 0 0         if (error != GIT_ENOTFOUND)
321 0           goto out;
322 0           error = 0;
323 0           continue;
324             }
325              
326 0 0         if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) {
327             /* TODO: warning if macro found in file below repo root */
328 0 0         if (!allow_macros)
329 0           continue;
330 0 0         if ((error = git_attr_cache__insert_macro(repo, rule)) < 0)
331 0           goto out;
332 0 0         } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0)
333 0           goto out;
334              
335 0           rule = NULL;
336             }
337              
338             out:
339             git_mutex_unlock(&attrs->lock);
340 1787           git_attr_rule__free(rule);
341              
342 1787           return error;
343             }
344              
345 4           uint32_t git_attr_file__name_hash(const char *name)
346             {
347 4           uint32_t h = 5381;
348             int c;
349 4 50         assert(name);
350 24 100         while ((c = (int)*name++) != 0)
351 20           h = ((h << 5) + h) + c;
352 4           return h;
353             }
354              
355 0           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 0           *value = NULL;
366              
367 0           name.name = attr;
368 0           name.name_hash = git_attr_file__name_hash(attr);
369              
370 0 0         git_attr_file__foreach_matching_rule(file, path, i, rule) {
    0          
371             size_t pos;
372              
373 0 0         if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
374 0           *value = ((git_attr_assignment *)
375 0           git_vector_get(&rule->assigns, pos))->value;
376 0           break;
377             }
378             }
379              
380 0           return 0;
381             }
382              
383 0           int git_attr_file__load_standalone(git_attr_file **out, const char *path)
384             {
385 0           git_buf content = GIT_BUF_INIT;
386 0           git_attr_file *file = NULL;
387             int error;
388              
389 0 0         if ((error = git_futils_readbuffer(&content, path)) < 0)
390 0           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 0 0         if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
    0          
398 0 0         (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
399 0           (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
400             goto out;
401              
402 0           *out = file;
403             out:
404 0 0         if (error < 0)
405 0           git_attr_file__free(file);
406 0           git_buf_dispose(&content);
407              
408 0           return error;
409             }
410              
411 2343           bool git_attr_fnmatch__match(
412             git_attr_fnmatch *match,
413             git_attr_path *path)
414             {
415 2343           const char *relpath = path->path;
416             const char *filename;
417 2343           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 2343 50         if (match->containing_dir) {
425 0 0         if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
426 0 0         if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
427 0           return 0;
428             } else {
429 0 0         if (git__prefixcmp(path->path, match->containing_dir))
430 0           return 0;
431             }
432              
433 0           relpath += match->containing_dir_length;
434             }
435              
436 2343 50         if (match->flags & GIT_ATTR_FNMATCH_ICASE)
437 0           flags |= WM_CASEFOLD;
438              
439 2343 50         if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
440 0           filename = relpath;
441 0           flags |= WM_PATHNAME;
442             } else {
443 2343           filename = path->basename;
444             }
445              
446 2343 50         if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
    0          
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 0 0         if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
    0          
455 0           path->basename == relpath)
456 0           return false;
457              
458             /* fail match if this is a file with same name as ignored folder */
459 0           samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
460 0 0         !strcasecmp(match->pattern, relpath) :
461 0           !strcmp(match->pattern, relpath);
462              
463 0 0         if (samename)
464 0           return false;
465              
466 0           return (wildmatch(match->pattern, relpath, flags) == WM_MATCH);
467             }
468              
469 2343           return (wildmatch(match->pattern, filename, flags) == WM_MATCH);
470             }
471              
472 0           bool git_attr_rule__match(
473             git_attr_rule *rule,
474             git_attr_path *path)
475             {
476 0           bool matched = git_attr_fnmatch__match(&rule->match, path);
477              
478 0 0         if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
479 0           matched = !matched;
480              
481 0           return matched;
482             }
483              
484 0           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 0           key.name = name;
490 0           key.name_hash = git_attr_file__name_hash(name);
491              
492 0 0         if (git_vector_bsearch(&pos, &rule->assigns, &key))
493 0           return NULL;
494              
495 0           return git_vector_get(&rule->assigns, pos);
496             }
497              
498 1332           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 1332           git_buf_init(&info->full, 0);
505              
506 1332 50         if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
507 0           return -1;
508              
509 1332           info->path = info->full.ptr + root;
510              
511             /* remove trailing slashes */
512 1853 50         while (info->full.size > 0) {
513 1853 100         if (info->full.ptr[info->full.size - 1] != '/')
514 1332           break;
515 521           info->full.size--;
516             }
517 1332           info->full.ptr[info->full.size] = '\0';
518              
519             /* skip leading slashes in path */
520 1402 100         while (*info->path == '/')
521 70           info->path++;
522              
523             /* find trailing basename component */
524 1332           info->basename = strrchr(info->path, '/');
525 1332 100         if (info->basename)
526 247           info->basename++;
527 1332 100         if (!info->basename || !*info->basename)
    50          
528 1085           info->basename = info->path;
529              
530 1332           switch (dir_flag)
531             {
532             case GIT_DIR_FLAG_FALSE:
533 0           info->is_dir = 0;
534 0           break;
535              
536             case GIT_DIR_FLAG_TRUE:
537 359           info->is_dir = 1;
538 359           break;
539              
540             case GIT_DIR_FLAG_UNKNOWN:
541             default:
542 973           info->is_dir = (int)git_path_isdir(info->full.ptr);
543 973           break;
544             }
545              
546 1332           return 0;
547             }
548              
549 1332           void git_attr_path__free(git_attr_path *info)
550             {
551 1332           git_buf_dispose(&info->full);
552 1332           info->path = NULL;
553 1332           info->basename = NULL;
554 1332           }
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 132           static size_t trailing_space_length(const char *p, size_t len)
593             {
594             size_t n, i;
595 132 50         for (n = len; n; n--) {
596 132 50         if (p[n-1] != ' ' && p[n-1] != '\t')
    50          
597 132           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 0           i = n;
607 0 0         while (i > 1 && p[i-2] == '\\')
    0          
608 0           i--;
609 0 0         if ((n - i) % 2)
610 0           break;
611             }
612 132           return len - n;
613             }
614              
615 132           static size_t unescape_spaces(char *str)
616             {
617 132           char *scan, *pos = str;
618 132           bool escaped = false;
619              
620 132 50         if (!str)
621 0           return 0;
622              
623 785 100         for (scan = str; *scan; scan++) {
624 653 50         if (!escaped && *scan == '\\') {
    50          
625 0           escaped = true;
626 0           continue;
627             }
628              
629             /* Only insert the escape character for escaped non-spaces */
630 653 50         if (escaped && !git__isspace(*scan))
    0          
631 0           *pos++ = '\\';
632              
633 653           *pos++ = *scan;
634 653           escaped = false;
635             }
636              
637 132 50         if (pos != scan)
638 0           *pos = '\0';
639              
640 132           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 160           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 160 50         assert(spec && base && *base);
    50          
    50          
659              
660 160 50         if (parse_optimized_patterns(spec, pool, *base))
661 0           return 0;
662              
663 160           spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
664 160           allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
665              
666 160           pattern = *base;
667              
668 160 50         while (!allow_space && git__isspace(*pattern))
    0          
669 0           pattern++;
670              
671 160 50         if (!*pattern || *pattern == '#' || *pattern == '\n' ||
    100          
    50          
    50          
672 0 0         (*pattern == '\r' && *(pattern + 1) == '\n')) {
673 28           *base = git__next_line(pattern);
674 28           return GIT_ENOTFOUND;
675             }
676              
677 132 50         if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
    0          
678 0 0         if (strncmp(pattern, "[attr]", 6) == 0) {
679 0           spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
680 0           pattern += 6;
681             }
682             /* else a character range like [a-e]* which is accepted */
683             }
684              
685 132 50         if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
    0          
686 0           spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
687 0           pattern++;
688             }
689              
690 132           slash_count = 0;
691 132           escaped = false;
692             /* Scan until a non-escaped whitespace. */
693 787 100         for (scan = pattern; *scan != '\0'; ++scan) {
694 699           char c = *scan;
695              
696 699 50         if (c == '\\' && !escaped) {
    0          
697 0           escaped = true;
698 0           continue;
699 699 100         } else if (git__isspace(c) && !escaped) {
    50          
700 44 50         if (!allow_space || (c != ' ' && c != '\t' && c != '\r'))
    50          
    50          
    50          
701             break;
702 655 100         } else if (c == '/') {
703 11           spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
704 11           slash_count++;
705              
706 11 100         if (slash_count == 1 && pattern == scan)
    50          
707 11           pattern++;
708 644 100         } else if (git__iswildcard(c) && !escaped) {
    50          
709             /* remember if we see an unescaped wildcard in pattern */
710 16           spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
711             }
712              
713 655           escaped = false;
714             }
715              
716 132           *base = scan;
717              
718 132 50         if ((spec->length = scan - pattern) == 0)
719 0           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 132 50         if (pattern[spec->length - 1] == '\r')
727 0 0         if (--spec->length == 0)
728 0           return GIT_ENOTFOUND;
729              
730             /* Remove trailing spaces. */
731 132           spec->length -= trailing_space_length(pattern, spec->length);
732              
733 132 50         if (spec->length == 0)
734 0           return GIT_ENOTFOUND;
735              
736 132 100         if (pattern[spec->length - 1] == '/') {
737 2           spec->length--;
738 2           spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
739 2 50         if (--slash_count <= 0)
740 2           spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
741             }
742              
743 132 50         if (context) {
744 0           char *slash = strrchr(context, '/');
745             size_t len;
746 0 0         if (slash) {
747             /* include the slash for easier matching */
748 0           len = slash - context + 1;
749 0           spec->containing_dir = git_pool_strndup(pool, context, len);
750 0           spec->containing_dir_length = len;
751             }
752             }
753              
754 132           spec->pattern = git_pool_strndup(pool, pattern, spec->length);
755              
756 132 50         if (!spec->pattern) {
757 0           *base = git__next_line(pattern);
758 0           return -1;
759             } else {
760             /* strip '\' that might have been used for internal whitespace */
761 132           spec->length = unescape_spaces(spec->pattern);
762             }
763              
764 132           return 0;
765             }
766              
767 160           static bool parse_optimized_patterns(
768             git_attr_fnmatch *spec,
769             git_pool *pool,
770             const char *pattern)
771             {
772 160 50         if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
    0          
    0          
773 0           spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
774 0           spec->pattern = git_pool_strndup(pool, pattern, 1);
775 0           spec->length = 1;
776              
777 0           return true;
778             }
779              
780 160           return false;
781             }
782              
783 64           static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
784             {
785 64           const git_attr_name *a = a_raw;
786 64           const git_attr_name *b = b_raw;
787              
788 64 100         if (b->name_hash < a->name_hash)
789 32           return 1;
790 32 50         else if (b->name_hash > a->name_hash)
791 32           return -1;
792             else
793 0           return strcmp(b->name, a->name);
794             }
795              
796 64           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 64           assign->name = NULL;
802 64           assign->value = NULL;
803 64           git__free(assign);
804 64           }
805              
806 0           static int merge_assignments(void **old_raw, void *new_raw)
807             {
808 0           git_attr_assignment **old = (git_attr_assignment **)old_raw;
809 0           git_attr_assignment *new = (git_attr_assignment *)new_raw;
810              
811 0 0         GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
    0          
812 0           *old = new;
813 0           return GIT_EEXISTS;
814             }
815              
816 16           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 16           const char *scan = *base;
824 16           git_attr_assignment *assign = NULL;
825              
826 16 50         assert(assigns && !assigns->length);
    50          
827              
828 16           git_vector_set_cmp(assigns, sort_by_hash_and_name);
829              
830 80 100         while (*scan && *scan != '\n') {
    50          
831             const char *name_start, *value_start;
832              
833             /* skip leading blanks */
834 112 100         while (git__isspace(*scan) && *scan != '\n') scan++;
    50          
835              
836             /* allocate assign if needed */
837 64 50         if (!assign) {
838 64           assign = git__calloc(1, sizeof(git_attr_assignment));
839 64 50         GIT_ERROR_CHECK_ALLOC(assign);
840 64           GIT_REFCOUNT_INC(assign);
841             }
842              
843 64           assign->name_hash = 5381;
844 64           assign->value = git_attr__true;
845              
846             /* look for magic name prefixes */
847 64 50         if (*scan == '-') {
848 64           assign->value = git_attr__false;
849 64           scan++;
850 0 0         } else if (*scan == '!') {
851 0           assign->value = git_attr__unset; /* explicit unspecified state */
852 0           scan++;
853 0 0         } else if (*scan == '#') /* comment rest of line */
854 0           break;
855              
856             /* find the name */
857 64           name_start = scan;
858 336 100         while (*scan && !git__isspace(*scan) && *scan != '=') {
    100          
    50          
859 272           assign->name_hash =
860 272           ((assign->name_hash << 5) + assign->name_hash) + *scan;
861 272           scan++;
862             }
863 64 50         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 0 0         while (*scan && !git__isspace(*scan)) scan++;
    0          
868 0           continue;
869             }
870              
871             /* allocate permanent storage for name */
872 64           assign->name = git_pool_strndup(pool, name_start, scan - name_start);
873 64 50         GIT_ERROR_CHECK_ALLOC(assign->name);
874              
875             /* if there is an equals sign, find the value */
876 64 50         if (*scan == '=') {
877 0 0         for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
    0          
878              
879             /* if we found a value, allocate permanent storage for it */
880 0 0         if (scan > value_start) {
881 0           assign->value = git_pool_strndup(pool, value_start, scan - value_start);
882 0 0         GIT_ERROR_CHECK_ALLOC(assign->value);
883             }
884             }
885              
886             /* expand macros (if given a repo with a macro cache) */
887 64 50         if (repo != NULL && assign->value == git_attr__true) {
    50          
888 0           git_attr_rule *macro =
889 0           git_attr_cache__lookup_macro(repo, assign->name);
890              
891 0 0         if (macro != NULL) {
892             unsigned int i;
893             git_attr_assignment *massign;
894              
895 0 0         git_vector_foreach(¯o->assigns, i, massign) {
896 0           GIT_REFCOUNT_INC(massign);
897              
898 0           error = git_vector_insert_sorted(
899             assigns, massign, &merge_assignments);
900 0 0         if (error < 0 && error != GIT_EEXISTS) {
    0          
901 0           git_attr_assignment__free(assign);
902 0           return error;
903             }
904             }
905             }
906             }
907              
908             /* insert allocated assign into vector */
909 64           error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
910 64 50         if (error < 0 && error != GIT_EEXISTS)
    0          
911 0           return error;
912              
913             /* clear assign since it is now "owned" by the vector */
914 64           assign = NULL;
915             }
916              
917 16 50         if (assign != NULL)
918 0           git_attr_assignment__free(assign);
919              
920 16           *base = git__next_line(scan);
921              
922 16 50         return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
923             }
924              
925 1847           static void git_attr_rule__clear(git_attr_rule *rule)
926             {
927             unsigned int i;
928             git_attr_assignment *assign;
929              
930 1847 100         if (!rule)
931 1787           return;
932              
933 60 100         if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
934 80 100         git_vector_foreach(&rule->assigns, i, assign)
935 64 50         GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
    50          
936 16           git_vector_free(&rule->assigns);
937             }
938              
939             /* match.pattern is stored in a git_pool, so no need to free */
940 60           rule->match.pattern = NULL;
941 60           rule->match.length = 0;
942             }
943              
944 1847           void git_attr_rule__free(git_attr_rule *rule)
945             {
946 1847           git_attr_rule__clear(rule);
947 1847           git__free(rule);
948 1847           }
949              
950 355           int git_attr_session__init(git_attr_session *session, git_repository *repo)
951             {
952 355 50         assert(repo);
953              
954 355           memset(session, 0, sizeof(*session));
955 355           session->key = git_atomic_inc(&repo->attr_session_key);
956              
957 355           return 0;
958             }
959              
960 355           void git_attr_session__free(git_attr_session *session)
961             {
962 355 50         if (!session)
963 0           return;
964              
965 355           git_buf_dispose(&session->sysdir);
966 355           git_buf_dispose(&session->tmp);
967              
968 355           memset(session, 0, sizeof(git_attr_session));
969             }