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