]> git.proxmox.com Git - libgit2.git/blob - src/attr.c
1f2643345dc8705d3ecbe0b57d04c01430c49e53
[libgit2.git] / src / attr.c
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 <ctype.h>
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 git_attr_t git_attr_value(const char *attr)
23 {
24 if (attr == NULL || attr == git_attr__unset)
25 return GIT_ATTR_UNSPECIFIED_T;
26
27 if (attr == git_attr__true)
28 return GIT_ATTR_TRUE_T;
29
30 if (attr == git_attr__false)
31 return GIT_ATTR_FALSE_T;
32
33 return GIT_ATTR_VALUE_T;
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 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 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 git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
60
61 assert(value && repo && name);
62
63 *value = NULL;
64
65 if (git_repository_is_bare(repo))
66 dir_flag = GIT_DIR_FLAG_FALSE;
67
68 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
69 return -1;
70
71 if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
72 goto cleanup;
73
74 memset(&attr, 0, sizeof(attr));
75 attr.name = name;
76 attr.name_hash = git_attr_file__name_hash(name);
77
78 git_vector_foreach(&files, i, file) {
79
80 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
81 size_t pos;
82
83 if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
84 *value = ((git_attr_assignment *)git_vector_get(
85 &rule->assigns, pos))->value;
86 goto cleanup;
87 }
88 }
89 }
90
91 cleanup:
92 release_attr_files(&files);
93 git_attr_path__free(&path);
94
95 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 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 git_vector files = GIT_VECTOR_INIT;
116 size_t i, j, k;
117 git_attr_file *file;
118 git_attr_rule *rule;
119 attr_get_many_info *info = NULL;
120 size_t num_found = 0;
121 git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
122
123 if (!num_attr)
124 return 0;
125
126 assert(values && repo && names);
127
128 if (git_repository_is_bare(repo))
129 dir_flag = GIT_DIR_FLAG_FALSE;
130
131 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
132 return -1;
133
134 if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
135 goto cleanup;
136
137 info = git__calloc(num_attr, sizeof(attr_get_many_info));
138 GIT_ERROR_CHECK_ALLOC(info);
139
140 git_vector_foreach(&files, i, file) {
141
142 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
143
144 for (k = 0; k < num_attr; k++) {
145 size_t pos;
146
147 if (info[k].found != NULL) /* already found assignment */
148 continue;
149
150 if (!info[k].name.name) {
151 info[k].name.name = names[k];
152 info[k].name.name_hash = git_attr_file__name_hash(names[k]);
153 }
154
155 if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
156 info[k].found = (git_attr_assignment *)
157 git_vector_get(&rule->assigns, pos);
158 values[k] = info[k].found->value;
159
160 if (++num_found == num_attr)
161 goto cleanup;
162 }
163 }
164 }
165 }
166
167 for (k = 0; k < num_attr; k++) {
168 if (!info[k].found)
169 values[k] = NULL;
170 }
171
172 cleanup:
173 release_attr_files(&files);
174 git_attr_path__free(&path);
175 git__free(info);
176
177 return error;
178 }
179
180 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 return git_attr_get_many_with_session(
189 values, repo, NULL, flags, pathname, num_attr, names);
190 }
191
192 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 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 git_strmap *seen = NULL;
207 git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
208
209 assert(repo && callback);
210
211 if (git_repository_is_bare(repo))
212 dir_flag = GIT_DIR_FLAG_FALSE;
213
214 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
215 return -1;
216
217 if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
218 (error = git_strmap_alloc(&seen)) < 0)
219 goto cleanup;
220
221 git_vector_foreach(&files, i, file) {
222
223 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
224
225 git_vector_foreach(&rule->assigns, k, assign) {
226 /* skip if higher priority assignment was already seen */
227 if (git_strmap_exists(seen, assign->name))
228 continue;
229
230 git_strmap_insert(seen, assign->name, assign, &error);
231 if (error < 0)
232 goto cleanup;
233
234 error = callback(assign->name, assign->value, payload);
235 if (error) {
236 git_error_set_after_callback(error);
237 goto cleanup;
238 }
239 }
240 }
241 }
242
243 cleanup:
244 git_strmap_free(seen);
245 release_attr_files(&files);
246 git_attr_path__free(&path);
247
248 return error;
249 }
250
251 static int preload_attr_file(
252 git_repository *repo,
253 git_attr_session *attr_session,
254 git_attr_file_source source,
255 const char *base,
256 const char *file)
257 {
258 int error;
259 git_attr_file *preload = NULL;
260
261 if (!file)
262 return 0;
263 if (!(error = git_attr_cache__get(
264 &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
265 git_attr_file__free(preload);
266
267 return error;
268 }
269
270 static int system_attr_file(
271 git_buf *out,
272 git_attr_session *attr_session)
273 {
274 int error;
275
276 if (!attr_session) {
277 error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
278
279 if (error == GIT_ENOTFOUND)
280 git_error_clear();
281
282 return error;
283 }
284
285 if (!attr_session->init_sysdir) {
286 error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
287
288 if (error == GIT_ENOTFOUND)
289 git_error_clear();
290 else if (error)
291 return error;
292
293 attr_session->init_sysdir = 1;
294 }
295
296 if (attr_session->sysdir.size == 0)
297 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 git_buf_attach_notowned(
304 out, attr_session->sysdir.ptr, attr_session->sysdir.size);
305 return 0;
306 }
307
308 static int attr_setup(git_repository *repo, git_attr_session *attr_session)
309 {
310 int error = 0;
311 const char *workdir = git_repository_workdir(repo);
312 git_index *idx = NULL;
313 git_buf path = GIT_BUF_INIT;
314
315 if (attr_session && attr_session->init_setup)
316 return 0;
317
318 if ((error = git_attr_cache__init(repo)) < 0)
319 return error;
320
321 /* preload attribute files that could contain macros so the
322 * definitions will be available for later file parsing
323 */
324
325 error = system_attr_file(&path, attr_session);
326
327 if (error == 0)
328 error = preload_attr_file(
329 repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr);
330
331 if (error != GIT_ENOTFOUND)
332 goto out;
333
334 if ((error = preload_attr_file(
335 repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
336 NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
337 goto out;
338
339 if ((error = git_repository_item_path(&path,
340 repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
341 goto out;
342
343 if ((error = preload_attr_file(
344 repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
345 path.ptr, GIT_ATTR_FILE_INREPO)) < 0)
346 goto out;
347
348 if (workdir != NULL &&
349 (error = preload_attr_file(
350 repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
351 goto out;
352
353 if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
354 (error = preload_attr_file(
355 repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
356 goto out;
357
358 if (attr_session)
359 attr_session->init_setup = 1;
360
361 out:
362 git_buf_dispose(&path);
363
364 return error;
365 }
366
367 int git_attr_add_macro(
368 git_repository *repo,
369 const char *name,
370 const char *values)
371 {
372 int error;
373 git_attr_rule *macro = NULL;
374 git_pool *pool;
375
376 if ((error = git_attr_cache__init(repo)) < 0)
377 return error;
378
379 macro = git__calloc(1, sizeof(git_attr_rule));
380 GIT_ERROR_CHECK_ALLOC(macro);
381
382 pool = &git_repository_attr_cache(repo)->pool;
383
384 macro->match.pattern = git_pool_strdup(pool, name);
385 GIT_ERROR_CHECK_ALLOC(macro->match.pattern);
386
387 macro->match.length = strlen(macro->match.pattern);
388 macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
389
390 error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
391
392 if (!error)
393 error = git_attr_cache__insert_macro(repo, macro);
394
395 if (error < 0)
396 git_attr_rule__free(macro);
397
398 return error;
399 }
400
401 typedef struct {
402 git_repository *repo;
403 git_attr_session *attr_session;
404 uint32_t flags;
405 const char *workdir;
406 git_index *index;
407 git_vector *files;
408 } attr_walk_up_info;
409
410 static int attr_decide_sources(
411 uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
412 {
413 int count = 0;
414
415 switch (flags & 0x03) {
416 case GIT_ATTR_CHECK_FILE_THEN_INDEX:
417 if (has_wd)
418 srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
419 if (has_index)
420 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
421 break;
422 case GIT_ATTR_CHECK_INDEX_THEN_FILE:
423 if (has_index)
424 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
425 if (has_wd)
426 srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
427 break;
428 case GIT_ATTR_CHECK_INDEX_ONLY:
429 if (has_index)
430 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
431 break;
432 }
433
434 return count;
435 }
436
437 static int push_attr_file(
438 git_repository *repo,
439 git_attr_session *attr_session,
440 git_vector *list,
441 git_attr_file_source source,
442 const char *base,
443 const char *filename)
444 {
445 int error = 0;
446 git_attr_file *file = NULL;
447
448 error = git_attr_cache__get(&file, repo, attr_session,
449 source, base, filename, git_attr_file__parse_buffer);
450
451 if (error < 0)
452 return error;
453
454 if (file != NULL) {
455 if ((error = git_vector_insert(list, file)) < 0)
456 git_attr_file__free(file);
457 }
458
459 return error;
460 }
461
462 static int push_one_attr(void *ref, const char *path)
463 {
464 int error = 0, n_src, i;
465 attr_walk_up_info *info = (attr_walk_up_info *)ref;
466 git_attr_file_source src[2];
467
468 n_src = attr_decide_sources(
469 info->flags, info->workdir != NULL, info->index != NULL, src);
470
471 for (i = 0; !error && i < n_src; ++i)
472 error = push_attr_file(info->repo, info->attr_session,
473 info->files, src[i], path, GIT_ATTR_FILE);
474
475 return error;
476 }
477
478 static void release_attr_files(git_vector *files)
479 {
480 size_t i;
481 git_attr_file *file;
482
483 git_vector_foreach(files, i, file) {
484 git_attr_file__free(file);
485 files->contents[i] = NULL;
486 }
487 git_vector_free(files);
488 }
489
490 static int collect_attr_files(
491 git_repository *repo,
492 git_attr_session *attr_session,
493 uint32_t flags,
494 const char *path,
495 git_vector *files)
496 {
497 int error = 0;
498 git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
499 const char *workdir = git_repository_workdir(repo);
500 attr_walk_up_info info = { NULL };
501
502 if ((error = attr_setup(repo, attr_session)) < 0)
503 return error;
504
505 /* Resolve path in a non-bare repo */
506 if (workdir != NULL)
507 error = git_path_find_dir(&dir, path, workdir);
508 else
509 error = git_path_dirname_r(&dir, path);
510 if (error < 0)
511 goto cleanup;
512
513 /* in precendence order highest to lowest:
514 * - $GIT_DIR/info/attributes
515 * - path components with .gitattributes
516 * - config core.attributesfile
517 * - $GIT_PREFIX/etc/gitattributes
518 */
519
520 error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO);
521 if (error < 0)
522 goto cleanup;
523
524 error = push_attr_file(
525 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
526 attrfile.ptr, GIT_ATTR_FILE_INREPO);
527 if (error < 0)
528 goto cleanup;
529
530 info.repo = repo;
531 info.attr_session = attr_session;
532 info.flags = flags;
533 info.workdir = workdir;
534 if (git_repository_index__weakptr(&info.index, repo) < 0)
535 git_error_clear(); /* no error even if there is no index */
536 info.files = files;
537
538 if (!strcmp(dir.ptr, "."))
539 error = push_one_attr(&info, "");
540 else
541 error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
542
543 if (error < 0)
544 goto cleanup;
545
546 if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
547 error = push_attr_file(
548 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
549 NULL, git_repository_attr_cache(repo)->cfg_attr_file);
550 if (error < 0)
551 goto cleanup;
552 }
553
554 if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
555 error = system_attr_file(&dir, attr_session);
556
557 if (!error)
558 error = push_attr_file(
559 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
560 NULL, dir.ptr);
561 else if (error == GIT_ENOTFOUND)
562 error = 0;
563 }
564
565 cleanup:
566 if (error < 0)
567 release_attr_files(files);
568 git_buf_dispose(&attrfile);
569 git_buf_dispose(&dir);
570
571 return error;
572 }