]> git.proxmox.com Git - libgit2.git/blame - src/attr.c
checkout: hold seen dir paths in a map
[libgit2.git] / src / attr.c
CommitLineData
157cef10 1#include "common.h"
73b51450 2#include "repository.h"
83634d38 3#include "sysdir.h"
ee1f0b1a 4#include "config.h"
7d490872 5#include "attr_file.h"
5540d947 6#include "ignore.h"
c07d9c95 7#include "git2/oid.h"
ee1f0b1a
RB
8#include <ctype.h>
9
c2b67043 10GIT__USE_STRMAP;
01fed0a8 11
0c9eacf3
VM
12const char *git_attr__true = "[internal]__TRUE__";
13const char *git_attr__false = "[internal]__FALSE__";
14const char *git_attr__unset = "[internal]__UNSET__";
15
16git_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
ee1f0b1a 30static int collect_attr_files(
f917481e 31 git_repository *repo,
9f779aac 32 git_attr_session *attr_session,
f917481e
RB
33 uint32_t flags,
34 const char *path,
35 git_vector *files);
ee1f0b1a 36
40ed4990 37static void release_attr_files(git_vector *files);
ee1f0b1a
RB
38
39int git_attr_get(
29e948de 40 const char **value,
0cb16fe9 41 git_repository *repo,
f917481e
RB
42 uint32_t flags,
43 const char *pathname,
29e948de 44 const char *name)
ee1f0b1a
RB
45{
46 int error;
47 git_attr_path path;
48 git_vector files = GIT_VECTOR_INIT;
b8457baa 49 size_t i, j;
ee1f0b1a
RB
50 git_attr_file *file;
51 git_attr_name attr;
52 git_attr_rule *rule;
53
3cf11eef
RB
54 assert(value && repo && name);
55
ee1f0b1a
RB
56 *value = NULL;
57
d58336dd
RB
58 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
59 return -1;
60
9f779aac 61 if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
d58336dd 62 goto cleanup;
ee1f0b1a 63
2e9d813b 64 memset(&attr, 0, sizeof(attr));
ee1f0b1a
RB
65 attr.name = name;
66 attr.name_hash = git_attr_file__name_hash(name);
67
68 git_vector_foreach(&files, i, file) {
69
70 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
11d9f6b3
PK
71 size_t pos;
72
73 if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
ee1f0b1a
RB
74 *value = ((git_attr_assignment *)git_vector_get(
75 &rule->assigns, pos))->value;
d58336dd 76 goto cleanup;
ee1f0b1a
RB
77 }
78 }
79 }
80
d58336dd 81cleanup:
40ed4990 82 release_attr_files(&files);
d58336dd 83 git_attr_path__free(&path);
ee1f0b1a
RB
84
85 return error;
86}
87
88
89typedef struct {
90 git_attr_name name;
91 git_attr_assignment *found;
92} attr_get_many_info;
93
9f779aac 94int git_attr_get_many_with_session(
29e948de 95 const char **values,
0cb16fe9 96 git_repository *repo,
9f779aac 97 git_attr_session *attr_session,
f917481e
RB
98 uint32_t flags,
99 const char *pathname,
0cb16fe9 100 size_t num_attr,
29e948de 101 const char **names)
ee1f0b1a
RB
102{
103 int error;
104 git_attr_path path;
105 git_vector files = GIT_VECTOR_INIT;
b8457baa 106 size_t i, j, k;
ee1f0b1a
RB
107 git_attr_file *file;
108 git_attr_rule *rule;
109 attr_get_many_info *info = NULL;
110 size_t num_found = 0;
111
3cf11eef
RB
112 if (!num_attr)
113 return 0;
114
115 assert(values && repo && names);
116
d58336dd
RB
117 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
118 return -1;
119
9f779aac 120 if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
d58336dd 121 goto cleanup;
ee1f0b1a 122
0d0fa7c3
RB
123 info = git__calloc(num_attr, sizeof(attr_get_many_info));
124 GITERR_CHECK_ALLOC(info);
ee1f0b1a
RB
125
126 git_vector_foreach(&files, i, file) {
127
128 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
129
130 for (k = 0; k < num_attr; k++) {
11d9f6b3 131 size_t pos;
ee1f0b1a
RB
132
133 if (info[k].found != NULL) /* already found assignment */
134 continue;
135
136 if (!info[k].name.name) {
137 info[k].name.name = names[k];
138 info[k].name.name_hash = git_attr_file__name_hash(names[k]);
139 }
140
11d9f6b3 141 if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
ee1f0b1a
RB
142 info[k].found = (git_attr_assignment *)
143 git_vector_get(&rule->assigns, pos);
144 values[k] = info[k].found->value;
145
146 if (++num_found == num_attr)
147 goto cleanup;
148 }
149 }
150 }
151 }
152
974774c7
RB
153 for (k = 0; k < num_attr; k++) {
154 if (!info[k].found)
155 values[k] = NULL;
156 }
157
ee1f0b1a 158cleanup:
40ed4990 159 release_attr_files(&files);
d58336dd 160 git_attr_path__free(&path);
ee1f0b1a
RB
161 git__free(info);
162
163 return error;
164}
165
9f779aac
ET
166int git_attr_get_many(
167 const char **values,
168 git_repository *repo,
169 uint32_t flags,
170 const char *pathname,
171 size_t num_attr,
172 const char **names)
173{
174 return git_attr_get_many_with_session(
175 values, repo, NULL, flags, pathname, num_attr, names);
176}
ee1f0b1a
RB
177
178int git_attr_foreach(
0cb16fe9 179 git_repository *repo,
f917481e
RB
180 uint32_t flags,
181 const char *pathname,
ee1f0b1a
RB
182 int (*callback)(const char *name, const char *value, void *payload),
183 void *payload)
184{
185 int error;
186 git_attr_path path;
187 git_vector files = GIT_VECTOR_INIT;
b8457baa 188 size_t i, j, k;
ee1f0b1a
RB
189 git_attr_file *file;
190 git_attr_rule *rule;
191 git_attr_assignment *assign;
c2b67043 192 git_strmap *seen = NULL;
ee1f0b1a 193
3cf11eef
RB
194 assert(repo && callback);
195
d58336dd
RB
196 if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
197 return -1;
198
9f779aac 199 if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
40ed4990 200 (error = git_strmap_alloc(&seen)) < 0)
d58336dd 201 goto cleanup;
ee1f0b1a 202
ee1f0b1a
RB
203 git_vector_foreach(&files, i, file) {
204
205 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
206
207 git_vector_foreach(&rule->assigns, k, assign) {
208 /* skip if higher priority assignment was already seen */
c2b67043 209 if (git_strmap_exists(seen, assign->name))
ee1f0b1a
RB
210 continue;
211
c2b67043 212 git_strmap_insert(seen, assign->name, assign, error);
5dca2010
RB
213 if (error < 0)
214 goto cleanup;
ee1f0b1a 215
c7b3e1b3
RB
216 error = callback(assign->name, assign->value, payload);
217 if (error) {
26c1cb91 218 giterr_set_after_callback(error);
ee1f0b1a 219 goto cleanup;
c7b3e1b3 220 }
ee1f0b1a
RB
221 }
222 }
223 }
224
225cleanup:
c2b67043 226 git_strmap_free(seen);
40ed4990 227 release_attr_files(&files);
d58336dd 228 git_attr_path__free(&path);
ee1f0b1a 229
ee1f0b1a
RB
230 return error;
231}
232
e3a2a04c
RB
233static int preload_attr_file(
234 git_repository *repo,
9f779aac 235 git_attr_session *attr_session,
e3a2a04c
RB
236 git_attr_file_source source,
237 const char *base,
238 const char *file)
239{
240 int error;
241 git_attr_file *preload = NULL;
242
243 if (!file)
244 return 0;
245 if (!(error = git_attr_cache__get(
9f779aac 246 &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
e3a2a04c
RB
247 git_attr_file__free(preload);
248
249 return error;
250}
251
d4b1b767
ET
252static int system_attr_file(
253 git_buf *out,
254 git_repository *repo,
255 git_attr_session *attr_session)
256{
257 int error;
258
259 if (!attr_session) {
260 error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
261
262 if (error == GIT_ENOTFOUND)
263 giterr_clear();
264
265 return error;
266 }
267
268 if (!attr_session->init_sysdir) {
269 error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
270
271 if (error == GIT_ENOTFOUND)
272 giterr_clear();
273 else if (error)
274 return error;
275
276 attr_session->init_sysdir = 1;
277 }
278
279 if (attr_session->sysdir.size == 0)
280 return GIT_ENOTFOUND;
281
282 /* We can safely provide a git_buf with no allocation (asize == 0) to
283 * a consumer. This allows them to treat this as a regular `git_buf`,
284 * but their call to `git_buf_free` will not attempt to free it.
285 */
286 out->ptr = attr_session->sysdir.ptr;
287 out->size = attr_session->sysdir.size;
288 out->asize = 0;
289 return 0;
290}
291
9f779aac 292static int attr_setup(git_repository *repo, git_attr_session *attr_session)
e3a2a04c
RB
293{
294 int error = 0;
295 const char *workdir = git_repository_workdir(repo);
296 git_index *idx = NULL;
297 git_buf sys = GIT_BUF_INIT;
298
d4b1b767 299 if (attr_session && attr_session->init_setup)
9f779aac
ET
300 return 0;
301
e3a2a04c
RB
302 if ((error = git_attr_cache__init(repo)) < 0)
303 return error;
304
305 /* preload attribute files that could contain macros so the
306 * definitions will be available for later file parsing
307 */
308
d4b1b767
ET
309 error = system_attr_file(&sys, repo, attr_session);
310
311 if (error == 0)
e3a2a04c 312 error = preload_attr_file(
9f779aac 313 repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
d4b1b767
ET
314 else if (error != GIT_ENOTFOUND)
315 return error;
316
317 git_buf_free(&sys);
e3a2a04c
RB
318
319 if ((error = preload_attr_file(
9f779aac 320 repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
e3a2a04c
RB
321 NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
322 return error;
323
324 if ((error = preload_attr_file(
9f779aac 325 repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
e3a2a04c
RB
326 git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
327 return error;
328
329 if (workdir != NULL &&
330 (error = preload_attr_file(
9f779aac 331 repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
e3a2a04c
RB
332 return error;
333
334 if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
335 (error = preload_attr_file(
9f779aac 336 repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
e3a2a04c
RB
337 return error;
338
9f779aac 339 if (attr_session)
d4b1b767 340 attr_session->init_setup = 1;
9f779aac 341
e3a2a04c
RB
342 return error;
343}
344
73b51450
RB
345int git_attr_add_macro(
346 git_repository *repo,
347 const char *name,
348 const char *values)
349{
350 int error;
351 git_attr_rule *macro = NULL;
19fa2bc1 352 git_pool *pool;
73b51450 353
ac16bd0a 354 if ((error = git_attr_cache__init(repo)) < 0)
e3a2a04c 355 return error;
73b51450
RB
356
357 macro = git__calloc(1, sizeof(git_attr_rule));
0d0fa7c3 358 GITERR_CHECK_ALLOC(macro);
73b51450 359
19fa2bc1
RB
360 pool = &git_repository_attr_cache(repo)->pool;
361
362 macro->match.pattern = git_pool_strdup(pool, name);
0d0fa7c3 363 GITERR_CHECK_ALLOC(macro->match.pattern);
73b51450
RB
364
365 macro->match.length = strlen(macro->match.pattern);
366 macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
367
19fa2bc1 368 error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
73b51450 369
0d0fa7c3 370 if (!error)
73b51450
RB
371 error = git_attr_cache__insert_macro(repo, macro);
372
0d0fa7c3 373 if (error < 0)
73b51450 374 git_attr_rule__free(macro);
73b51450
RB
375
376 return error;
377}
378
0cfcff5d
RB
379typedef struct {
380 git_repository *repo;
9f779aac 381 git_attr_session *attr_session;
f917481e
RB
382 uint32_t flags;
383 const char *workdir;
384 git_index *index;
0cfcff5d
RB
385 git_vector *files;
386} attr_walk_up_info;
387
7d490872 388static int attr_decide_sources(
823c0e9c 389 uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
f917481e
RB
390{
391 int count = 0;
392
393 switch (flags & 0x03) {
394 case GIT_ATTR_CHECK_FILE_THEN_INDEX:
395 if (has_wd)
823c0e9c 396 srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
f917481e 397 if (has_index)
823c0e9c 398 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
f917481e
RB
399 break;
400 case GIT_ATTR_CHECK_INDEX_THEN_FILE:
401 if (has_index)
823c0e9c 402 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
f917481e 403 if (has_wd)
823c0e9c 404 srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
f917481e
RB
405 break;
406 case GIT_ATTR_CHECK_INDEX_ONLY:
407 if (has_index)
823c0e9c 408 srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
f917481e
RB
409 break;
410 }
411
412 return count;
413}
414
7d490872
RB
415static int push_attr_file(
416 git_repository *repo,
9f779aac 417 git_attr_session *attr_session,
7d490872 418 git_vector *list,
823c0e9c 419 git_attr_file_source source,
7d490872
RB
420 const char *base,
421 const char *filename)
422{
423 int error = 0;
424 git_attr_file *file = NULL;
425
9f779aac
ET
426 error = git_attr_cache__get(&file, repo, attr_session,
427 source, base, filename, git_attr_file__parse_buffer);
428
2e9d813b
RB
429 if (error < 0)
430 return error;
431
432 if (file != NULL) {
433 if ((error = git_vector_insert(list, file)) < 0)
434 git_attr_file__free(file);
435 }
7d490872
RB
436
437 return error;
438}
439
bbb988a5 440static int push_one_attr(void *ref, const char *path)
0cfcff5d 441{
f917481e 442 int error = 0, n_src, i;
0cfcff5d 443 attr_walk_up_info *info = (attr_walk_up_info *)ref;
823c0e9c 444 git_attr_file_source src[2];
f917481e 445
7d490872 446 n_src = attr_decide_sources(
f917481e
RB
447 info->flags, info->workdir != NULL, info->index != NULL, src);
448
449 for (i = 0; !error && i < n_src; ++i)
9f779aac
ET
450 error = push_attr_file(info->repo, info->attr_session,
451 info->files, src[i], path, GIT_ATTR_FILE);
f917481e 452
25e0b157 453 return error;
0cfcff5d
RB
454}
455
40ed4990
RB
456static void release_attr_files(git_vector *files)
457{
458 size_t i;
459 git_attr_file *file;
460
461 git_vector_foreach(files, i, file) {
462 git_attr_file__free(file);
463 files->contents[i] = NULL;
464 }
465 git_vector_free(files);
466}
467
ee1f0b1a 468static int collect_attr_files(
f917481e 469 git_repository *repo,
9f779aac 470 git_attr_session *attr_session,
f917481e
RB
471 uint32_t flags,
472 const char *path,
473 git_vector *files)
ee1f0b1a 474{
52e9120c 475 int error = 0;
ee1f0b1a 476 git_buf dir = GIT_BUF_INIT;
ee1f0b1a 477 const char *workdir = git_repository_workdir(repo);
dab89f9b 478 attr_walk_up_info info = { NULL };
ee1f0b1a 479
9f779aac 480 if ((error = attr_setup(repo, attr_session)) < 0)
2e9d813b 481 return error;
ee1f0b1a 482
cba285d3
RB
483 /* Resolve path in a non-bare repo */
484 if (workdir != NULL)
f917481e 485 error = git_path_find_dir(&dir, path, workdir);
a2b4407d
T
486 else
487 error = git_path_dirname_r(&dir, path);
ab43ad2f 488 if (error < 0)
ee1f0b1a
RB
489 goto cleanup;
490
ee1f0b1a
RB
491 /* in precendence order highest to lowest:
492 * - $GIT_DIR/info/attributes
493 * - path components with .gitattributes
494 * - config core.attributesfile
495 * - $GIT_PREFIX/etc/gitattributes
496 */
497
f917481e 498 error = push_attr_file(
9f779aac 499 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
7d490872 500 git_repository_path(repo), GIT_ATTR_FILE_INREPO);
ab43ad2f 501 if (error < 0)
ee1f0b1a
RB
502 goto cleanup;
503
9f779aac
ET
504 info.repo = repo;
505 info.attr_session = attr_session;
f917481e
RB
506 info.flags = flags;
507 info.workdir = workdir;
508 if (git_repository_index__weakptr(&info.index, repo) < 0)
509 giterr_clear(); /* no error even if there is no index */
0cfcff5d 510 info.files = files;
f917481e 511
4e964117 512 if (!strcmp(dir.ptr, "."))
a2b4407d 513 error = push_one_attr(&info, "");
4e964117 514 else
a2b4407d 515 error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
a2b4407d 516
ab43ad2f 517 if (error < 0)
ee1f0b1a
RB
518 goto cleanup;
519
95dfb031 520 if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
f917481e 521 error = push_attr_file(
9f779aac 522 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
7d490872 523 NULL, git_repository_attr_cache(repo)->cfg_attr_file);
95dfb031
RB
524 if (error < 0)
525 goto cleanup;
ee1f0b1a
RB
526 }
527
f917481e 528 if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
d4b1b767
ET
529 error = system_attr_file(&dir, repo, attr_session);
530
f917481e 531 if (!error)
7d490872 532 error = push_attr_file(
9f779aac
ET
533 repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
534 NULL, dir.ptr);
d4b1b767 535 else if (error == GIT_ENOTFOUND)
f917481e
RB
536 error = 0;
537 }
ee1f0b1a
RB
538
539 cleanup:
ab43ad2f 540 if (error < 0)
40ed4990 541 release_attr_files(files);
ee1f0b1a
RB
542 git_buf_free(&dir);
543
544 return error;
545}