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