]> git.proxmox.com Git - libgit2.git/blob - src/attr.c
Merge pull request #2066 from libgit2/rb/builtin-diff-drivers
[libgit2.git] / src / attr.c
1 #include "common.h"
2 #include "repository.h"
3 #include "fileops.h"
4 #include "config.h"
5 #include "attr.h"
6 #include "ignore.h"
7 #include "git2/oid.h"
8 #include <ctype.h>
9
10 GIT__USE_STRMAP;
11
12 const char *git_attr__true = "[internal]__TRUE__";
13 const char *git_attr__false = "[internal]__FALSE__";
14 const char *git_attr__unset = "[internal]__UNSET__";
15
16 git_attr_t git_attr_value(const char *attr)
17 {
18 if (attr == NULL || attr == git_attr__unset)
19 return GIT_ATTR_UNSPECIFIED_T;
20
21 if (attr == git_attr__true)
22 return GIT_ATTR_TRUE_T;
23
24 if (attr == git_attr__false)
25 return GIT_ATTR_FALSE_T;
26
27 return GIT_ATTR_VALUE_T;
28 }
29
30 static int collect_attr_files(
31 git_repository *repo,
32 uint32_t flags,
33 const char *path,
34 git_vector *files);
35
36
37 int git_attr_get(
38 const char **value,
39 git_repository *repo,
40 uint32_t flags,
41 const char *pathname,
42 const char *name)
43 {
44 int error;
45 git_attr_path path;
46 git_vector files = GIT_VECTOR_INIT;
47 size_t i, j;
48 git_attr_file *file;
49 git_attr_name attr;
50 git_attr_rule *rule;
51
52 *value = NULL;
53
54 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
55 return -1;
56
57 if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
58 goto cleanup;
59
60 attr.name = name;
61 attr.name_hash = git_attr_file__name_hash(name);
62
63 git_vector_foreach(&files, i, file) {
64
65 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
66 size_t pos;
67
68 if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
69 *value = ((git_attr_assignment *)git_vector_get(
70 &rule->assigns, pos))->value;
71 goto cleanup;
72 }
73 }
74 }
75
76 cleanup:
77 git_vector_free(&files);
78 git_attr_path__free(&path);
79
80 return error;
81 }
82
83
84 typedef struct {
85 git_attr_name name;
86 git_attr_assignment *found;
87 } attr_get_many_info;
88
89 int git_attr_get_many(
90 const char **values,
91 git_repository *repo,
92 uint32_t flags,
93 const char *pathname,
94 size_t num_attr,
95 const char **names)
96 {
97 int error;
98 git_attr_path path;
99 git_vector files = GIT_VECTOR_INIT;
100 size_t i, j, k;
101 git_attr_file *file;
102 git_attr_rule *rule;
103 attr_get_many_info *info = NULL;
104 size_t num_found = 0;
105
106 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
107 return -1;
108
109 if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
110 goto cleanup;
111
112 info = git__calloc(num_attr, sizeof(attr_get_many_info));
113 GITERR_CHECK_ALLOC(info);
114
115 git_vector_foreach(&files, i, file) {
116
117 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
118
119 for (k = 0; k < num_attr; k++) {
120 size_t pos;
121
122 if (info[k].found != NULL) /* already found assignment */
123 continue;
124
125 if (!info[k].name.name) {
126 info[k].name.name = names[k];
127 info[k].name.name_hash = git_attr_file__name_hash(names[k]);
128 }
129
130 if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
131 info[k].found = (git_attr_assignment *)
132 git_vector_get(&rule->assigns, pos);
133 values[k] = info[k].found->value;
134
135 if (++num_found == num_attr)
136 goto cleanup;
137 }
138 }
139 }
140 }
141
142 for (k = 0; k < num_attr; k++) {
143 if (!info[k].found)
144 values[k] = NULL;
145 }
146
147 cleanup:
148 git_vector_free(&files);
149 git_attr_path__free(&path);
150 git__free(info);
151
152 return error;
153 }
154
155
156 int git_attr_foreach(
157 git_repository *repo,
158 uint32_t flags,
159 const char *pathname,
160 int (*callback)(const char *name, const char *value, void *payload),
161 void *payload)
162 {
163 int error;
164 git_attr_path path;
165 git_vector files = GIT_VECTOR_INIT;
166 size_t i, j, k;
167 git_attr_file *file;
168 git_attr_rule *rule;
169 git_attr_assignment *assign;
170 git_strmap *seen = NULL;
171
172 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
173 return -1;
174
175 if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
176 goto cleanup;
177
178 seen = git_strmap_alloc();
179 GITERR_CHECK_ALLOC(seen);
180
181 git_vector_foreach(&files, i, file) {
182
183 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
184
185 git_vector_foreach(&rule->assigns, k, assign) {
186 /* skip if higher priority assignment was already seen */
187 if (git_strmap_exists(seen, assign->name))
188 continue;
189
190 git_strmap_insert(seen, assign->name, assign, error);
191 if (error < 0)
192 goto cleanup;
193
194 error = callback(assign->name, assign->value, payload);
195 if (error) {
196 giterr_set_after_callback(error);
197 goto cleanup;
198 }
199 }
200 }
201 }
202
203 cleanup:
204 git_strmap_free(seen);
205 git_vector_free(&files);
206 git_attr_path__free(&path);
207
208 return error;
209 }
210
211
212 int git_attr_add_macro(
213 git_repository *repo,
214 const char *name,
215 const char *values)
216 {
217 int error;
218 git_attr_rule *macro = NULL;
219 git_pool *pool;
220
221 if (git_attr_cache__init(repo) < 0)
222 return -1;
223
224 macro = git__calloc(1, sizeof(git_attr_rule));
225 GITERR_CHECK_ALLOC(macro);
226
227 pool = &git_repository_attr_cache(repo)->pool;
228
229 macro->match.pattern = git_pool_strdup(pool, name);
230 GITERR_CHECK_ALLOC(macro->match.pattern);
231
232 macro->match.length = strlen(macro->match.pattern);
233 macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
234
235 error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
236
237 if (!error)
238 error = git_attr_cache__insert_macro(repo, macro);
239
240 if (error < 0)
241 git_attr_rule__free(macro);
242
243 return error;
244 }
245
246 bool git_attr_cache__is_cached(
247 git_repository *repo, git_attr_file_source source, const char *path)
248 {
249 git_buf cache_key = GIT_BUF_INIT;
250 git_strmap *files = git_repository_attr_cache(repo)->files;
251 const char *workdir = git_repository_workdir(repo);
252 bool rval;
253
254 if (workdir && git__prefixcmp(path, workdir) == 0)
255 path += strlen(workdir);
256 if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0)
257 return false;
258
259 rval = git_strmap_exists(files, git_buf_cstr(&cache_key));
260
261 git_buf_free(&cache_key);
262
263 return rval;
264 }
265
266 static int load_attr_file(
267 const char **data,
268 git_futils_filestamp *stamp,
269 const char *filename)
270 {
271 int error;
272 git_buf content = GIT_BUF_INIT;
273
274 error = git_futils_filestamp_check(stamp, filename);
275 if (error < 0)
276 return error;
277
278 /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
279 * we tell the caller not to reparse this file...
280 */
281 if (!error)
282 return GIT_ENOTFOUND;
283
284 error = git_futils_readbuffer(&content, filename);
285 if (error < 0) {
286 /* convert error into ENOTFOUND so failed permissions / invalid
287 * file type don't actually stop the operation in progress.
288 */
289 return GIT_ENOTFOUND;
290
291 /* TODO: once warnings are available, issue a warning callback */
292 }
293
294 *data = git_buf_detach(&content);
295
296 return 0;
297 }
298
299 static int load_attr_blob_from_index(
300 const char **content,
301 git_blob **blob,
302 git_repository *repo,
303 const git_oid *old_oid,
304 const char *relfile)
305 {
306 int error;
307 size_t pos;
308 git_index *index;
309 const git_index_entry *entry;
310
311 if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
312 (error = git_index_find(&pos, index, relfile)) < 0)
313 return error;
314
315 entry = git_index_get_byindex(index, pos);
316
317 if (old_oid && git_oid__cmp(old_oid, &entry->id) == 0)
318 return GIT_ENOTFOUND;
319
320 if ((error = git_blob_lookup(blob, repo, &entry->id)) < 0)
321 return error;
322
323 *content = git_blob_rawcontent(*blob);
324 return 0;
325 }
326
327 static int load_attr_from_cache(
328 git_attr_file **file,
329 git_attr_cache *cache,
330 git_attr_file_source source,
331 const char *relative_path)
332 {
333 git_buf cache_key = GIT_BUF_INIT;
334 khiter_t cache_pos;
335
336 *file = NULL;
337
338 if (!cache || !cache->files)
339 return 0;
340
341 if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
342 return -1;
343
344 cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
345
346 git_buf_free(&cache_key);
347
348 if (git_strmap_valid_index(cache->files, cache_pos))
349 *file = git_strmap_value_at(cache->files, cache_pos);
350
351 return 0;
352 }
353
354 int git_attr_cache__internal_file(
355 git_repository *repo,
356 const char *filename,
357 git_attr_file **file)
358 {
359 int error = 0;
360 git_attr_cache *cache = git_repository_attr_cache(repo);
361 khiter_t cache_pos = git_strmap_lookup_index(cache->files, filename);
362
363 if (git_strmap_valid_index(cache->files, cache_pos)) {
364 *file = git_strmap_value_at(cache->files, cache_pos);
365 return 0;
366 }
367
368 if (git_attr_file__new(file, 0, filename, &cache->pool) < 0)
369 return -1;
370
371 git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
372 if (error > 0)
373 error = 0;
374
375 return error;
376 }
377
378 int git_attr_cache__push_file(
379 git_repository *repo,
380 const char *base,
381 const char *filename,
382 git_attr_file_source source,
383 git_attr_file_parser parse,
384 void* parsedata,
385 git_vector *stack)
386 {
387 int error = 0;
388 git_buf path = GIT_BUF_INIT;
389 const char *workdir = git_repository_workdir(repo);
390 const char *relfile, *content = NULL;
391 git_attr_cache *cache = git_repository_attr_cache(repo);
392 git_attr_file *file = NULL;
393 git_blob *blob = NULL;
394 git_futils_filestamp stamp;
395
396 assert(filename && stack);
397
398 /* join base and path as needed */
399 if (base != NULL && git_path_root(filename) < 0) {
400 if (git_buf_joinpath(&path, base, filename) < 0)
401 return -1;
402 filename = path.ptr;
403 }
404
405 relfile = filename;
406 if (workdir && git__prefixcmp(relfile, workdir) == 0)
407 relfile += strlen(workdir);
408
409 /* check cache */
410 if (load_attr_from_cache(&file, cache, source, relfile) < 0)
411 return -1;
412
413 /* if not in cache, load data, parse, and cache */
414
415 if (source == GIT_ATTR_FILE_FROM_FILE) {
416 git_futils_filestamp_set(
417 &stamp, file ? &file->cache_data.stamp : NULL);
418
419 error = load_attr_file(&content, &stamp, filename);
420 } else {
421 error = load_attr_blob_from_index(&content, &blob,
422 repo, file ? &file->cache_data.oid : NULL, relfile);
423 }
424
425 if (error) {
426 /* not finding a file is not an error for this function */
427 if (error == GIT_ENOTFOUND) {
428 giterr_clear();
429 error = 0;
430 }
431 goto finish;
432 }
433
434 /* if we got here, we have to parse and/or reparse the file */
435 if (file)
436 git_attr_file__clear_rules(file);
437 else {
438 error = git_attr_file__new(&file, source, relfile, &cache->pool);
439 if (error < 0)
440 goto finish;
441 }
442
443 if (parse && (error = parse(repo, parsedata, content, file)) < 0)
444 goto finish;
445
446 git_strmap_insert(cache->files, file->key, file, error); //-V595
447 if (error > 0)
448 error = 0;
449
450 /* remember "cache buster" file signature */
451 if (blob)
452 git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
453 else
454 git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
455
456 finish:
457 /* push file onto vector if we found one*/
458 if (!error && file != NULL)
459 error = git_vector_insert(stack, file);
460
461 if (error != 0)
462 git_attr_file__free(file);
463
464 if (blob)
465 git_blob_free(blob);
466 else
467 git__free((void *)content);
468
469 git_buf_free(&path);
470
471 return error;
472 }
473
474 #define push_attr_file(R,S,B,F) \
475 git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
476
477 typedef struct {
478 git_repository *repo;
479 uint32_t flags;
480 const char *workdir;
481 git_index *index;
482 git_vector *files;
483 } attr_walk_up_info;
484
485 int git_attr_cache__decide_sources(
486 uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
487 {
488 int count = 0;
489
490 switch (flags & 0x03) {
491 case GIT_ATTR_CHECK_FILE_THEN_INDEX:
492 if (has_wd)
493 srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
494 if (has_index)
495 srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
496 break;
497 case GIT_ATTR_CHECK_INDEX_THEN_FILE:
498 if (has_index)
499 srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
500 if (has_wd)
501 srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
502 break;
503 case GIT_ATTR_CHECK_INDEX_ONLY:
504 if (has_index)
505 srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
506 break;
507 }
508
509 return count;
510 }
511
512 static int push_one_attr(void *ref, git_buf *path)
513 {
514 int error = 0, n_src, i;
515 attr_walk_up_info *info = (attr_walk_up_info *)ref;
516 git_attr_file_source src[2];
517
518 n_src = git_attr_cache__decide_sources(
519 info->flags, info->workdir != NULL, info->index != NULL, src);
520
521 for (i = 0; !error && i < n_src; ++i)
522 error = git_attr_cache__push_file(
523 info->repo, path->ptr, GIT_ATTR_FILE, src[i],
524 git_attr_file__parse_buffer, NULL, info->files);
525
526 return error;
527 }
528
529 static int collect_attr_files(
530 git_repository *repo,
531 uint32_t flags,
532 const char *path,
533 git_vector *files)
534 {
535 int error;
536 git_buf dir = GIT_BUF_INIT;
537 const char *workdir = git_repository_workdir(repo);
538 attr_walk_up_info info = { NULL };
539
540 if (git_attr_cache__init(repo) < 0 ||
541 git_vector_init(files, 4, NULL) < 0)
542 return -1;
543
544 /* Resolve path in a non-bare repo */
545 if (workdir != NULL)
546 error = git_path_find_dir(&dir, path, workdir);
547 else
548 error = git_path_dirname_r(&dir, path);
549 if (error < 0)
550 goto cleanup;
551
552 /* in precendence order highest to lowest:
553 * - $GIT_DIR/info/attributes
554 * - path components with .gitattributes
555 * - config core.attributesfile
556 * - $GIT_PREFIX/etc/gitattributes
557 */
558
559 error = push_attr_file(
560 repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
561 if (error < 0)
562 goto cleanup;
563
564 info.repo = repo;
565 info.flags = flags;
566 info.workdir = workdir;
567 if (git_repository_index__weakptr(&info.index, repo) < 0)
568 giterr_clear(); /* no error even if there is no index */
569 info.files = files;
570
571 error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
572 if (error < 0)
573 goto cleanup;
574
575 if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
576 error = push_attr_file(
577 repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
578 if (error < 0)
579 goto cleanup;
580 }
581
582 if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
583 error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
584 if (!error)
585 error = push_attr_file(repo, files, NULL, dir.ptr);
586 else if (error == GIT_ENOTFOUND) {
587 giterr_clear();
588 error = 0;
589 }
590 }
591
592 cleanup:
593 if (error < 0)
594 git_vector_free(files);
595 git_buf_free(&dir);
596
597 return error;
598 }
599
600 static int attr_cache__lookup_path(
601 char **out, git_config *cfg, const char *key, const char *fallback)
602 {
603 git_buf buf = GIT_BUF_INIT;
604 int error;
605 const git_config_entry *entry = NULL;
606
607 *out = NULL;
608
609 if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
610 return error;
611
612 if (entry) {
613 const char *cfgval = entry->value;
614
615 /* expand leading ~/ as needed */
616 if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
617 !git_futils_find_global_file(&buf, &cfgval[2]))
618 *out = git_buf_detach(&buf);
619 else if (cfgval)
620 *out = git__strdup(cfgval);
621
622 }
623 else if (!git_futils_find_xdg_file(&buf, fallback))
624 *out = git_buf_detach(&buf);
625
626 git_buf_free(&buf);
627
628 return error;
629 }
630
631 int git_attr_cache__init(git_repository *repo)
632 {
633 int ret;
634 git_attr_cache *cache = git_repository_attr_cache(repo);
635 git_config *cfg;
636
637 if (cache->initialized)
638 return 0;
639
640 /* cache config settings for attributes and ignores */
641 if (git_repository_config__weakptr(&cfg, repo) < 0)
642 return -1;
643
644 ret = attr_cache__lookup_path(
645 &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
646 if (ret < 0)
647 return ret;
648
649 ret = attr_cache__lookup_path(
650 &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
651 if (ret < 0)
652 return ret;
653
654 /* allocate hashtable for attribute and ignore file contents */
655 if (cache->files == NULL) {
656 cache->files = git_strmap_alloc();
657 GITERR_CHECK_ALLOC(cache->files);
658 }
659
660 /* allocate hashtable for attribute macros */
661 if (cache->macros == NULL) {
662 cache->macros = git_strmap_alloc();
663 GITERR_CHECK_ALLOC(cache->macros);
664 }
665
666 /* allocate string pool */
667 if (git_pool_init(&cache->pool, 1, 0) < 0)
668 return -1;
669
670 cache->initialized = 1;
671
672 /* insert default macros */
673 return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
674 }
675
676 void git_attr_cache_flush(
677 git_repository *repo)
678 {
679 git_attr_cache *cache;
680
681 if (!repo)
682 return;
683
684 cache = git_repository_attr_cache(repo);
685
686 if (cache->files != NULL) {
687 git_attr_file *file;
688
689 git_strmap_foreach_value(cache->files, file, {
690 git_attr_file__free(file);
691 });
692
693 git_strmap_free(cache->files);
694 }
695
696 if (cache->macros != NULL) {
697 git_attr_rule *rule;
698
699 git_strmap_foreach_value(cache->macros, rule, {
700 git_attr_rule__free(rule);
701 });
702
703 git_strmap_free(cache->macros);
704 }
705
706 git_pool_clear(&cache->pool);
707
708 git__free(cache->cfg_attr_file);
709 cache->cfg_attr_file = NULL;
710
711 git__free(cache->cfg_excl_file);
712 cache->cfg_excl_file = NULL;
713
714 cache->initialized = 0;
715 }
716
717 int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
718 {
719 git_strmap *macros = git_repository_attr_cache(repo)->macros;
720 int error;
721
722 /* TODO: generate warning log if (macro->assigns.length == 0) */
723 if (macro->assigns.length == 0)
724 return 0;
725
726 git_strmap_insert(macros, macro->match.pattern, macro, error);
727 return (error < 0) ? -1 : 0;
728 }
729
730 git_attr_rule *git_attr_cache__lookup_macro(
731 git_repository *repo, const char *name)
732 {
733 git_strmap *macros = git_repository_attr_cache(repo)->macros;
734 khiter_t pos;
735
736 pos = git_strmap_lookup_index(macros, name);
737
738 if (!git_strmap_valid_index(macros, pos))
739 return NULL;
740
741 return (git_attr_rule *)git_strmap_value_at(macros, pos);
742 }
743