File Coverage

deps/libgit2/src/attr_file.c
Criterion Covered Total %
statement 263 447 58.8
branch 149 366 40.7
condition n/a
subroutine n/a
pod n/a
total 412 813 50.6


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