File Coverage

deps/libgit2/src/attr.c
Criterion Covered Total %
statement 161 255 63.1
branch 87 208 41.8
condition n/a
subroutine n/a
pod n/a
total 248 463 53.5


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.h"
9              
10             #include "repository.h"
11             #include "sysdir.h"
12             #include "config.h"
13             #include "attr_file.h"
14             #include "ignore.h"
15             #include "git2/oid.h"
16             #include
17              
18             const char *git_attr__true = "[internal]__TRUE__";
19             const char *git_attr__false = "[internal]__FALSE__";
20             const char *git_attr__unset = "[internal]__UNSET__";
21              
22 2168           git_attr_value_t git_attr_value(const char *attr)
23             {
24 2168 100         if (attr == NULL || attr == git_attr__unset)
    50          
25 1940           return GIT_ATTR_VALUE_UNSPECIFIED;
26              
27 228 50         if (attr == git_attr__true)
28 228           return GIT_ATTR_VALUE_TRUE;
29              
30 0 0         if (attr == git_attr__false)
31 0           return GIT_ATTR_VALUE_FALSE;
32              
33 0           return GIT_ATTR_VALUE_STRING;
34             }
35              
36             static int collect_attr_files(
37             git_repository *repo,
38             git_attr_session *attr_session,
39             uint32_t flags,
40             const char *path,
41             git_vector *files);
42              
43             static void release_attr_files(git_vector *files);
44              
45 4           int git_attr_get(
46             const char **value,
47             git_repository *repo,
48             uint32_t flags,
49             const char *pathname,
50             const char *name)
51             {
52             int error;
53             git_attr_path path;
54 4           git_vector files = GIT_VECTOR_INIT;
55             size_t i, j;
56             git_attr_file *file;
57             git_attr_name attr;
58             git_attr_rule *rule;
59 4           git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
60              
61 4 50         assert(value && repo && name);
    50          
    50          
62              
63 4           *value = NULL;
64              
65 4 50         if (git_repository_is_bare(repo))
66 0           dir_flag = GIT_DIR_FLAG_FALSE;
67              
68 4 50         if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
69 0           return -1;
70              
71 4 50         if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
72 0           goto cleanup;
73              
74 4           memset(&attr, 0, sizeof(attr));
75 4           attr.name = name;
76 4           attr.name_hash = git_attr_file__name_hash(name);
77              
78 12 100         git_vector_foreach(&files, i, file) {
79              
80 8 0         git_attr_file__foreach_matching_rule(file, &path, j, rule) {
    50          
81             size_t pos;
82              
83 0 0         if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
84 0           *value = ((git_attr_assignment *)git_vector_get(
85 0           &rule->assigns, pos))->value;
86 0           goto cleanup;
87             }
88             }
89             }
90              
91             cleanup:
92 4           release_attr_files(&files);
93 4           git_attr_path__free(&path);
94              
95 4           return error;
96             }
97              
98              
99             typedef struct {
100             git_attr_name name;
101             git_attr_assignment *found;
102             } attr_get_many_info;
103              
104 561           int git_attr_get_many_with_session(
105             const char **values,
106             git_repository *repo,
107             git_attr_session *attr_session,
108             uint32_t flags,
109             const char *pathname,
110             size_t num_attr,
111             const char **names)
112             {
113             int error;
114             git_attr_path path;
115 561           git_vector files = GIT_VECTOR_INIT;
116             size_t i, j, k;
117             git_attr_file *file;
118             git_attr_rule *rule;
119 561           attr_get_many_info *info = NULL;
120 561           size_t num_found = 0;
121 561           git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
122              
123 561 50         if (!num_attr)
124 0           return 0;
125              
126 561 50         assert(values && repo && names);
    50          
    50          
127              
128 561 50         if (git_repository_is_bare(repo))
129 0           dir_flag = GIT_DIR_FLAG_FALSE;
130              
131 561 50         if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
132 0           return -1;
133              
134 561 50         if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
135 0           goto cleanup;
136              
137 561           info = git__calloc(num_attr, sizeof(attr_get_many_info));
138 561 50         GIT_ERROR_CHECK_ALLOC(info);
139              
140 1799 100         git_vector_foreach(&files, i, file) {
141              
142 1238 0         git_attr_file__foreach_matching_rule(file, &path, j, rule) {
    50          
143              
144 0 0         for (k = 0; k < num_attr; k++) {
145             size_t pos;
146              
147 0 0         if (info[k].found != NULL) /* already found assignment */
148 0           continue;
149              
150 0 0         if (!info[k].name.name) {
151 0           info[k].name.name = names[k];
152 0           info[k].name.name_hash = git_attr_file__name_hash(names[k]);
153             }
154              
155 0 0         if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
156 0           info[k].found = (git_attr_assignment *)
157 0           git_vector_get(&rule->assigns, pos);
158 0           values[k] = info[k].found->value;
159              
160 0 0         if (++num_found == num_attr)
161 0           goto cleanup;
162             }
163             }
164             }
165             }
166              
167 1578 100         for (k = 0; k < num_attr; k++) {
168 1017 50         if (!info[k].found)
169 1017           values[k] = NULL;
170             }
171              
172             cleanup:
173 561           release_attr_files(&files);
174 561           git_attr_path__free(&path);
175 561           git__free(info);
176              
177 561           return error;
178             }
179              
180 0           int git_attr_get_many(
181             const char **values,
182             git_repository *repo,
183             uint32_t flags,
184             const char *pathname,
185             size_t num_attr,
186             const char **names)
187             {
188 0           return git_attr_get_many_with_session(
189             values, repo, NULL, flags, pathname, num_attr, names);
190             }
191              
192 0           int git_attr_foreach(
193             git_repository *repo,
194             uint32_t flags,
195             const char *pathname,
196             int (*callback)(const char *name, const char *value, void *payload),
197             void *payload)
198             {
199             int error;
200             git_attr_path path;
201 0           git_vector files = GIT_VECTOR_INIT;
202             size_t i, j, k;
203             git_attr_file *file;
204             git_attr_rule *rule;
205             git_attr_assignment *assign;
206 0           git_strmap *seen = NULL;
207 0           git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
208              
209 0 0         assert(repo && callback);
    0          
210              
211 0 0         if (git_repository_is_bare(repo))
212 0           dir_flag = GIT_DIR_FLAG_FALSE;
213              
214 0 0         if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
215 0           return -1;
216              
217 0 0         if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
    0          
218             (error = git_strmap_new(&seen)) < 0)
219             goto cleanup;
220              
221 0 0         git_vector_foreach(&files, i, file) {
222              
223 0 0         git_attr_file__foreach_matching_rule(file, &path, j, rule) {
    0          
224              
225 0 0         git_vector_foreach(&rule->assigns, k, assign) {
226             /* skip if higher priority assignment was already seen */
227 0 0         if (git_strmap_exists(seen, assign->name))
228 0           continue;
229              
230 0 0         if ((error = git_strmap_set(seen, assign->name, assign)) < 0)
231 0           goto cleanup;
232              
233 0           error = callback(assign->name, assign->value, payload);
234 0 0         if (error) {
235 0           git_error_set_after_callback(error);
236 0           goto cleanup;
237             }
238             }
239             }
240             }
241              
242             cleanup:
243 0           git_strmap_free(seen);
244 0           release_attr_files(&files);
245 0           git_attr_path__free(&path);
246              
247 0           return error;
248             }
249              
250 1804           static int preload_attr_file(
251             git_repository *repo,
252             git_attr_session *attr_session,
253             git_attr_file_source source,
254             const char *base,
255             const char *file,
256             bool allow_macros)
257             {
258             int error;
259 1804           git_attr_file *preload = NULL;
260              
261 1804 100         if (!file)
262 451           return 0;
263 1353 50         if (!(error = git_attr_cache__get(&preload, repo, attr_session, source, base, file,
264             git_attr_file__parse_buffer, allow_macros)))
265 1353           git_attr_file__free(preload);
266              
267 1804           return error;
268             }
269              
270 1016           static int system_attr_file(
271             git_buf *out,
272             git_attr_session *attr_session)
273             {
274             int error;
275              
276 1016 100         if (!attr_session) {
277 794           error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
278              
279 794 50         if (error == GIT_ENOTFOUND)
280 794           git_error_clear();
281              
282 794           return error;
283             }
284              
285 222 100         if (!attr_session->init_sysdir) {
286 54           error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
287              
288 54 50         if (error == GIT_ENOTFOUND)
289 54           git_error_clear();
290 0 0         else if (error)
291 0           return error;
292              
293 54           attr_session->init_sysdir = 1;
294             }
295              
296 222 50         if (attr_session->sysdir.size == 0)
297 222           return GIT_ENOTFOUND;
298              
299             /* We can safely provide a git_buf with no allocation (asize == 0) to
300             * a consumer. This allows them to treat this as a regular `git_buf`,
301             * but their call to `git_buf_dispose` will not attempt to free it.
302             */
303 0           git_buf_attach_notowned(
304 0           out, attr_session->sysdir.ptr, attr_session->sysdir.size);
305 0           return 0;
306             }
307              
308 565           static int attr_setup(
309             git_repository *repo,
310             git_attr_session *attr_session,
311             uint32_t flags)
312             {
313 565           git_buf path = GIT_BUF_INIT;
314 565           git_index *idx = NULL;
315             const char *workdir;
316 565           int error = 0;
317              
318 565 100         if (attr_session && attr_session->init_setup)
    100          
319 114           return 0;
320              
321 451 50         if ((error = git_attr_cache__init(repo)) < 0)
322 0           return error;
323              
324             /*
325             * Preload attribute files that could contain macros so the
326             * definitions will be available for later file parsing.
327             */
328              
329 451 50         if ((error = system_attr_file(&path, attr_session)) < 0 ||
    0          
330             (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
331 0           NULL, path.ptr, true)) < 0) {
332 451 50         if (error != GIT_ENOTFOUND)
333 0           goto out;
334             }
335              
336 451 50         if ((error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
337 451           NULL, git_repository_attr_cache(repo)->cfg_attr_file, true)) < 0)
338 0           goto out;
339              
340 451           git_buf_clear(&path); /* git_repository_item_path expects an empty buffer, because it uses git_buf_set */
341 451 50         if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
    50          
342             (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
343 451           path.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
344 0 0         if (error != GIT_ENOTFOUND)
345 0           goto out;
346             }
347              
348 451 50         if ((workdir = git_repository_workdir(repo)) != NULL &&
    50          
349             (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
350             workdir, GIT_ATTR_FILE, true)) < 0)
351 0           goto out;
352              
353 451 50         if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
    50          
354             (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_INDEX,
355             NULL, GIT_ATTR_FILE, true)) < 0)
356             goto out;
357              
358 451 50         if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0 &&
    0          
359             (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_HEAD,
360             NULL, GIT_ATTR_FILE, true)) < 0)
361 0           goto out;
362              
363 451 100         if (attr_session)
364 54           attr_session->init_setup = 1;
365              
366             out:
367 451           git_buf_dispose(&path);
368              
369 565           return error;
370             }
371              
372 16           int git_attr_add_macro(
373             git_repository *repo,
374             const char *name,
375             const char *values)
376             {
377             int error;
378 16           git_attr_rule *macro = NULL;
379             git_pool *pool;
380              
381 16 50         if ((error = git_attr_cache__init(repo)) < 0)
382 0           return error;
383              
384 16           macro = git__calloc(1, sizeof(git_attr_rule));
385 16 50         GIT_ERROR_CHECK_ALLOC(macro);
386              
387 16           pool = &git_repository_attr_cache(repo)->pool;
388              
389 16           macro->match.pattern = git_pool_strdup(pool, name);
390 16 50         GIT_ERROR_CHECK_ALLOC(macro->match.pattern);
391              
392 16           macro->match.length = strlen(macro->match.pattern);
393 16           macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
394              
395 16           error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values);
396              
397 16 50         if (!error)
398 16           error = git_attr_cache__insert_macro(repo, macro);
399              
400 16 50         if (error < 0)
401 0           git_attr_rule__free(macro);
402              
403 16           return error;
404             }
405              
406             typedef struct {
407             git_repository *repo;
408             git_attr_session *attr_session;
409             uint32_t flags;
410             const char *workdir;
411             git_index *index;
412             git_vector *files;
413             } attr_walk_up_info;
414              
415 681           static int attr_decide_sources(
416             uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
417             {
418 681           int count = 0;
419              
420 681           switch (flags & 0x03) {
421             case GIT_ATTR_CHECK_FILE_THEN_INDEX:
422 681 50         if (has_wd)
423 681           srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
424 681 50         if (has_index)
425 681           srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
426 681           break;
427             case GIT_ATTR_CHECK_INDEX_THEN_FILE:
428 0 0         if (has_index)
429 0           srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
430 0 0         if (has_wd)
431 0           srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
432 0           break;
433             case GIT_ATTR_CHECK_INDEX_ONLY:
434 0 0         if (has_index)
435 0           srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
436 0           break;
437             }
438              
439 681 50         if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0)
440 0           srcs[count++] = GIT_ATTR_FILE__FROM_HEAD;
441              
442 681           return count;
443             }
444              
445 1927           static int push_attr_file(
446             git_repository *repo,
447             git_attr_session *attr_session,
448             git_vector *list,
449             git_attr_file_source source,
450             const char *base,
451             const char *filename,
452             bool allow_macros)
453             {
454 1927           int error = 0;
455 1927           git_attr_file *file = NULL;
456              
457 1927           error = git_attr_cache__get(&file, repo, attr_session,
458             source, base, filename, git_attr_file__parse_buffer, allow_macros);
459              
460 1927 50         if (error < 0)
461 0           return error;
462              
463 1927 100         if (file != NULL) {
464 1246 50         if ((error = git_vector_insert(list, file)) < 0)
465 0           git_attr_file__free(file);
466             }
467              
468 1927           return error;
469             }
470              
471 681           static int push_one_attr(void *ref, const char *path)
472             {
473 681           attr_walk_up_info *info = (attr_walk_up_info *)ref;
474             git_attr_file_source src[GIT_ATTR_FILE_NUM_SOURCES];
475 681           int error = 0, n_src, i;
476             bool allow_macros;
477              
478 681           n_src = attr_decide_sources(
479 1362           info->flags, info->workdir != NULL, info->index != NULL, src);
480 681 50         allow_macros = info->workdir ? !strcmp(info->workdir, path) : false;
    100          
481              
482 2043 50         for (i = 0; !error && i < n_src; ++i)
    100          
483 1362           error = push_attr_file(info->repo, info->attr_session, info->files,
484             src[i], path, GIT_ATTR_FILE, allow_macros);
485              
486 681           return error;
487             }
488              
489 565           static void release_attr_files(git_vector *files)
490             {
491             size_t i;
492             git_attr_file *file;
493              
494 1811 100         git_vector_foreach(files, i, file) {
495 1246           git_attr_file__free(file);
496 1246           files->contents[i] = NULL;
497             }
498 565           git_vector_free(files);
499 565           }
500              
501 565           static int collect_attr_files(
502             git_repository *repo,
503             git_attr_session *attr_session,
504             uint32_t flags,
505             const char *path,
506             git_vector *files)
507             {
508 565           int error = 0;
509 565           git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
510 565           const char *workdir = git_repository_workdir(repo);
511 565           attr_walk_up_info info = { NULL };
512              
513 565 50         if ((error = attr_setup(repo, attr_session, flags)) < 0)
514 0           return error;
515              
516             /* Resolve path in a non-bare repo */
517 565 50         if (workdir != NULL)
518 565           error = git_path_find_dir(&dir, path, workdir);
519             else
520 0           error = git_path_dirname_r(&dir, path);
521 565 50         if (error < 0)
522 0           goto cleanup;
523              
524             /* in precendence order highest to lowest:
525             * - $GIT_DIR/info/attributes
526             * - path components with .gitattributes
527             * - config core.attributesfile
528             * - $GIT_PREFIX/etc/gitattributes
529             */
530              
531 565 50         if ((error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
    50          
532             (error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
533 565           attrfile.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
534 0 0         if (error != GIT_ENOTFOUND)
535 0           goto cleanup;
536             }
537              
538 565           info.repo = repo;
539 565           info.attr_session = attr_session;
540 565           info.flags = flags;
541 565           info.workdir = workdir;
542 565 50         if (git_repository_index__weakptr(&info.index, repo) < 0)
543 0           git_error_clear(); /* no error even if there is no index */
544 565           info.files = files;
545              
546 565 50         if (!strcmp(dir.ptr, "."))
547 0           error = push_one_attr(&info, "");
548             else
549 565           error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
550              
551 565 50         if (error < 0)
552 0           goto cleanup;
553              
554 565 50         if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
555 0           error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
556 0           NULL, git_repository_attr_cache(repo)->cfg_attr_file, true);
557 0 0         if (error < 0)
558 0           goto cleanup;
559             }
560              
561 565 50         if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
562 565           error = system_attr_file(&dir, attr_session);
563              
564 565 50         if (!error)
565 0           error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
566 0           NULL, dir.ptr, true);
567 565 50         else if (error == GIT_ENOTFOUND)
568 565           error = 0;
569             }
570              
571             cleanup:
572 565 50         if (error < 0)
573 0           release_attr_files(files);
574 565           git_buf_dispose(&attrfile);
575 565           git_buf_dispose(&dir);
576              
577 565           return error;
578             }