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