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