2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
10 #include "repository.h"
13 #include "attr_file.h"
18 const char *git_attr__true
= "[internal]__TRUE__";
19 const char *git_attr__false
= "[internal]__FALSE__";
20 const char *git_attr__unset
= "[internal]__UNSET__";
22 git_attr_value_t
git_attr_value(const char *attr
)
24 if (attr
== NULL
|| attr
== git_attr__unset
)
25 return GIT_ATTR_VALUE_UNSPECIFIED
;
27 if (attr
== git_attr__true
)
28 return GIT_ATTR_VALUE_TRUE
;
30 if (attr
== git_attr__false
)
31 return GIT_ATTR_VALUE_FALSE
;
33 return GIT_ATTR_VALUE_STRING
;
36 static int collect_attr_files(
38 git_attr_session
*attr_session
,
43 static void release_attr_files(git_vector
*files
);
54 git_vector files
= GIT_VECTOR_INIT
;
59 git_dir_flag dir_flag
= GIT_DIR_FLAG_UNKNOWN
;
61 assert(value
&& repo
&& name
);
65 if (git_repository_is_bare(repo
))
66 dir_flag
= GIT_DIR_FLAG_FALSE
;
68 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
), dir_flag
) < 0)
71 if ((error
= collect_attr_files(repo
, NULL
, flags
, pathname
, &files
)) < 0)
74 memset(&attr
, 0, sizeof(attr
));
76 attr
.name_hash
= git_attr_file__name_hash(name
);
78 git_vector_foreach(&files
, i
, file
) {
80 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
83 if (!git_vector_bsearch(&pos
, &rule
->assigns
, &attr
)) {
84 *value
= ((git_attr_assignment
*)git_vector_get(
85 &rule
->assigns
, pos
))->value
;
92 release_attr_files(&files
);
93 git_attr_path__free(&path
);
101 git_attr_assignment
*found
;
102 } attr_get_many_info
;
104 int git_attr_get_many_with_session(
106 git_repository
*repo
,
107 git_attr_session
*attr_session
,
109 const char *pathname
,
115 git_vector files
= GIT_VECTOR_INIT
;
119 attr_get_many_info
*info
= NULL
;
120 size_t num_found
= 0;
121 git_dir_flag dir_flag
= GIT_DIR_FLAG_UNKNOWN
;
126 assert(values
&& repo
&& names
);
128 if (git_repository_is_bare(repo
))
129 dir_flag
= GIT_DIR_FLAG_FALSE
;
131 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
), dir_flag
) < 0)
134 if ((error
= collect_attr_files(repo
, attr_session
, flags
, pathname
, &files
)) < 0)
137 info
= git__calloc(num_attr
, sizeof(attr_get_many_info
));
138 GIT_ERROR_CHECK_ALLOC(info
);
140 git_vector_foreach(&files
, i
, file
) {
142 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
144 for (k
= 0; k
< num_attr
; k
++) {
147 if (info
[k
].found
!= NULL
) /* already found assignment */
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
]);
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
;
160 if (++num_found
== num_attr
)
167 for (k
= 0; k
< num_attr
; k
++) {
173 release_attr_files(&files
);
174 git_attr_path__free(&path
);
180 int git_attr_get_many(
182 git_repository
*repo
,
184 const char *pathname
,
188 return git_attr_get_many_with_session(
189 values
, repo
, NULL
, flags
, pathname
, num_attr
, names
);
192 int git_attr_foreach(
193 git_repository
*repo
,
195 const char *pathname
,
196 int (*callback
)(const char *name
, const char *value
, void *payload
),
201 git_vector files
= GIT_VECTOR_INIT
;
205 git_attr_assignment
*assign
;
206 git_strmap
*seen
= NULL
;
207 git_dir_flag dir_flag
= GIT_DIR_FLAG_UNKNOWN
;
209 assert(repo
&& callback
);
211 if (git_repository_is_bare(repo
))
212 dir_flag
= GIT_DIR_FLAG_FALSE
;
214 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
), dir_flag
) < 0)
217 if ((error
= collect_attr_files(repo
, NULL
, flags
, pathname
, &files
)) < 0 ||
218 (error
= git_strmap_new(&seen
)) < 0)
221 git_vector_foreach(&files
, i
, file
) {
223 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
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
))
230 if ((error
= git_strmap_set(seen
, assign
->name
, assign
)) < 0)
233 error
= callback(assign
->name
, assign
->value
, payload
);
235 git_error_set_after_callback(error
);
243 git_strmap_free(seen
);
244 release_attr_files(&files
);
245 git_attr_path__free(&path
);
250 static int preload_attr_file(
251 git_repository
*repo
,
252 git_attr_session
*attr_session
,
253 git_attr_file_source source
,
259 git_attr_file
*preload
= NULL
;
263 if (!(error
= git_attr_cache__get(&preload
, repo
, attr_session
, source
, base
, file
,
264 git_attr_file__parse_buffer
, allow_macros
)))
265 git_attr_file__free(preload
);
270 static int system_attr_file(
272 git_attr_session
*attr_session
)
277 error
= git_sysdir_find_system_file(out
, GIT_ATTR_FILE_SYSTEM
);
279 if (error
== GIT_ENOTFOUND
)
285 if (!attr_session
->init_sysdir
) {
286 error
= git_sysdir_find_system_file(&attr_session
->sysdir
, GIT_ATTR_FILE_SYSTEM
);
288 if (error
== GIT_ENOTFOUND
)
293 attr_session
->init_sysdir
= 1;
296 if (attr_session
->sysdir
.size
== 0)
297 return GIT_ENOTFOUND
;
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.
303 git_buf_attach_notowned(
304 out
, attr_session
->sysdir
.ptr
, attr_session
->sysdir
.size
);
308 static int attr_setup(
309 git_repository
*repo
,
310 git_attr_session
*attr_session
,
313 git_buf path
= GIT_BUF_INIT
;
314 git_index
*idx
= NULL
;
318 if (attr_session
&& attr_session
->init_setup
)
321 if ((error
= git_attr_cache__init(repo
)) < 0)
325 * Preload attribute files that could contain macros so the
326 * definitions will be available for later file parsing.
329 if ((error
= system_attr_file(&path
, attr_session
)) < 0 ||
330 (error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_FILE
,
331 NULL
, path
.ptr
, true)) < 0) {
332 if (error
!= GIT_ENOTFOUND
)
336 if ((error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_FILE
,
337 NULL
, git_repository_attr_cache(repo
)->cfg_attr_file
, true)) < 0)
340 git_buf_clear(&path
); /* git_repository_item_path expects an empty buffer, because it uses git_buf_set */
341 if ((error
= git_repository_item_path(&path
, repo
, GIT_REPOSITORY_ITEM_INFO
)) < 0 ||
342 (error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_FILE
,
343 path
.ptr
, GIT_ATTR_FILE_INREPO
, true)) < 0) {
344 if (error
!= GIT_ENOTFOUND
)
348 if ((workdir
= git_repository_workdir(repo
)) != NULL
&&
349 (error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_FILE
,
350 workdir
, GIT_ATTR_FILE
, true)) < 0)
353 if ((error
= git_repository_index__weakptr(&idx
, repo
)) < 0 ||
354 (error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_INDEX
,
355 NULL
, GIT_ATTR_FILE
, true)) < 0)
358 if ((flags
& GIT_ATTR_CHECK_INCLUDE_HEAD
) != 0 &&
359 (error
= preload_attr_file(repo
, attr_session
, GIT_ATTR_FILE__FROM_HEAD
,
360 NULL
, GIT_ATTR_FILE
, true)) < 0)
364 attr_session
->init_setup
= 1;
367 git_buf_dispose(&path
);
372 int git_attr_add_macro(
373 git_repository
*repo
,
378 git_attr_rule
*macro
= NULL
;
381 if ((error
= git_attr_cache__init(repo
)) < 0)
384 macro
= git__calloc(1, sizeof(git_attr_rule
));
385 GIT_ERROR_CHECK_ALLOC(macro
);
387 pool
= &git_repository_attr_cache(repo
)->pool
;
389 macro
->match
.pattern
= git_pool_strdup(pool
, name
);
390 GIT_ERROR_CHECK_ALLOC(macro
->match
.pattern
);
392 macro
->match
.length
= strlen(macro
->match
.pattern
);
393 macro
->match
.flags
= GIT_ATTR_FNMATCH_MACRO
;
395 error
= git_attr_assignment__parse(repo
, pool
, ¯o
->assigns
, &values
);
398 error
= git_attr_cache__insert_macro(repo
, macro
);
401 git_attr_rule__free(macro
);
407 git_repository
*repo
;
408 git_attr_session
*attr_session
;
415 static int attr_decide_sources(
416 uint32_t flags
, bool has_wd
, bool has_index
, git_attr_file_source
*srcs
)
420 switch (flags
& 0x03) {
421 case GIT_ATTR_CHECK_FILE_THEN_INDEX
:
423 srcs
[count
++] = GIT_ATTR_FILE__FROM_FILE
;
425 srcs
[count
++] = GIT_ATTR_FILE__FROM_INDEX
;
427 case GIT_ATTR_CHECK_INDEX_THEN_FILE
:
429 srcs
[count
++] = GIT_ATTR_FILE__FROM_INDEX
;
431 srcs
[count
++] = GIT_ATTR_FILE__FROM_FILE
;
433 case GIT_ATTR_CHECK_INDEX_ONLY
:
435 srcs
[count
++] = GIT_ATTR_FILE__FROM_INDEX
;
439 if ((flags
& GIT_ATTR_CHECK_INCLUDE_HEAD
) != 0)
440 srcs
[count
++] = GIT_ATTR_FILE__FROM_HEAD
;
445 static int push_attr_file(
446 git_repository
*repo
,
447 git_attr_session
*attr_session
,
449 git_attr_file_source source
,
451 const char *filename
,
455 git_attr_file
*file
= NULL
;
457 error
= git_attr_cache__get(&file
, repo
, attr_session
,
458 source
, base
, filename
, git_attr_file__parse_buffer
, allow_macros
);
464 if ((error
= git_vector_insert(list
, file
)) < 0)
465 git_attr_file__free(file
);
471 static int push_one_attr(void *ref
, const char *path
)
473 attr_walk_up_info
*info
= (attr_walk_up_info
*)ref
;
474 git_attr_file_source src
[GIT_ATTR_FILE_NUM_SOURCES
];
475 int error
= 0, n_src
, i
;
478 n_src
= attr_decide_sources(
479 info
->flags
, info
->workdir
!= NULL
, info
->index
!= NULL
, src
);
480 allow_macros
= info
->workdir
? !strcmp(info
->workdir
, path
) : false;
482 for (i
= 0; !error
&& i
< n_src
; ++i
)
483 error
= push_attr_file(info
->repo
, info
->attr_session
, info
->files
,
484 src
[i
], path
, GIT_ATTR_FILE
, allow_macros
);
489 static void release_attr_files(git_vector
*files
)
494 git_vector_foreach(files
, i
, file
) {
495 git_attr_file__free(file
);
496 files
->contents
[i
] = NULL
;
498 git_vector_free(files
);
501 static int collect_attr_files(
502 git_repository
*repo
,
503 git_attr_session
*attr_session
,
509 git_buf dir
= GIT_BUF_INIT
, attrfile
= GIT_BUF_INIT
;
510 const char *workdir
= git_repository_workdir(repo
);
511 attr_walk_up_info info
= { NULL
};
513 if ((error
= attr_setup(repo
, attr_session
, flags
)) < 0)
516 /* Resolve path in a non-bare repo */
518 error
= git_path_find_dir(&dir
, path
, workdir
);
520 error
= git_path_dirname_r(&dir
, path
);
524 /* in precendence order highest to lowest:
525 * - $GIT_DIR/info/attributes
526 * - path components with .gitattributes
527 * - config core.attributesfile
528 * - $GIT_PREFIX/etc/gitattributes
531 if ((error
= git_repository_item_path(&attrfile
, repo
, GIT_REPOSITORY_ITEM_INFO
)) < 0 ||
532 (error
= push_attr_file(repo
, attr_session
, files
, GIT_ATTR_FILE__FROM_FILE
,
533 attrfile
.ptr
, GIT_ATTR_FILE_INREPO
, true)) < 0) {
534 if (error
!= GIT_ENOTFOUND
)
539 info
.attr_session
= attr_session
;
541 info
.workdir
= workdir
;
542 if (git_repository_index__weakptr(&info
.index
, repo
) < 0)
543 git_error_clear(); /* no error even if there is no index */
546 if (!strcmp(dir
.ptr
, "."))
547 error
= push_one_attr(&info
, "");
549 error
= git_path_walk_up(&dir
, workdir
, push_one_attr
, &info
);
554 if (git_repository_attr_cache(repo
)->cfg_attr_file
!= NULL
) {
555 error
= push_attr_file(repo
, attr_session
, files
, GIT_ATTR_FILE__FROM_FILE
,
556 NULL
, git_repository_attr_cache(repo
)->cfg_attr_file
, true);
561 if ((flags
& GIT_ATTR_CHECK_NO_SYSTEM
) == 0) {
562 error
= system_attr_file(&dir
, attr_session
);
565 error
= push_attr_file(repo
, attr_session
, files
, GIT_ATTR_FILE__FROM_FILE
,
566 NULL
, dir
.ptr
, true);
567 else if (error
== GIT_ENOTFOUND
)
573 release_attr_files(files
);
574 git_buf_dispose(&attrfile
);
575 git_buf_dispose(&dir
);