File Coverage

deps/libgit2/src/libgit2/attr_file.c
Criterion Covered Total %
statement 271 481 56.3
branch 151 384 39.3
condition n/a
subroutine n/a
pod n/a
total 422 865 48.7


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