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_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 
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 	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 
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)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 
git_attr_get_many(const char ** values,git_repository * repo,uint32_t flags,const char * pathname,size_t num_attr,const char ** names)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 
git_attr_foreach(git_repository * repo,uint32_t flags,const char * pathname,int (* callback)(const char * name,const char * value,void * payload),void * payload)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 
preload_attr_file(git_repository * repo,git_attr_session * attr_session,git_attr_file_source source,const char * base,const char * file)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 
system_attr_file(git_buf * out,git_attr_session * attr_session)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 
attr_setup(git_repository * repo,git_attr_session * attr_session)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 
git_attr_add_macro(git_repository * repo,const char * name,const char * values)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 
attr_decide_sources(uint32_t flags,bool has_wd,bool has_index,git_attr_file_source * srcs)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 
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)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 
push_one_attr(void * ref,const char * path)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 
release_attr_files(git_vector * files)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 
collect_attr_files(git_repository * repo,git_attr_session * attr_session,uint32_t flags,const char * path,git_vector * files)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 }
573