2 #include "repository.h"
12 const char *git_attr__true
= "[internal]__TRUE__";
13 const char *git_attr__false
= "[internal]__FALSE__";
14 const char *git_attr__unset
= "[internal]__UNSET__";
16 git_attr_t
git_attr_value(const char *attr
)
18 if (attr
== NULL
|| attr
== git_attr__unset
)
19 return GIT_ATTR_UNSPECIFIED_T
;
21 if (attr
== git_attr__true
)
22 return GIT_ATTR_TRUE_T
;
24 if (attr
== git_attr__false
)
25 return GIT_ATTR_FALSE_T
;
27 return GIT_ATTR_VALUE_T
;
30 static int collect_attr_files(
46 git_vector files
= GIT_VECTOR_INIT
;
54 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
)) < 0)
57 if ((error
= collect_attr_files(repo
, flags
, pathname
, &files
)) < 0)
61 attr
.name_hash
= git_attr_file__name_hash(name
);
63 git_vector_foreach(&files
, i
, file
) {
65 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
68 if (!git_vector_bsearch(&pos
, &rule
->assigns
, &attr
)) {
69 *value
= ((git_attr_assignment
*)git_vector_get(
70 &rule
->assigns
, pos
))->value
;
77 git_vector_free(&files
);
78 git_attr_path__free(&path
);
86 git_attr_assignment
*found
;
89 int git_attr_get_many(
99 git_vector files
= GIT_VECTOR_INIT
;
103 attr_get_many_info
*info
= NULL
;
104 size_t num_found
= 0;
106 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
)) < 0)
109 if ((error
= collect_attr_files(repo
, flags
, pathname
, &files
)) < 0)
112 info
= git__calloc(num_attr
, sizeof(attr_get_many_info
));
113 GITERR_CHECK_ALLOC(info
);
115 git_vector_foreach(&files
, i
, file
) {
117 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
119 for (k
= 0; k
< num_attr
; k
++) {
122 if (info
[k
].found
!= NULL
) /* already found assignment */
125 if (!info
[k
].name
.name
) {
126 info
[k
].name
.name
= names
[k
];
127 info
[k
].name
.name_hash
= git_attr_file__name_hash(names
[k
]);
130 if (!git_vector_bsearch(&pos
, &rule
->assigns
, &info
[k
].name
)) {
131 info
[k
].found
= (git_attr_assignment
*)
132 git_vector_get(&rule
->assigns
, pos
);
133 values
[k
] = info
[k
].found
->value
;
135 if (++num_found
== num_attr
)
142 for (k
= 0; k
< num_attr
; k
++) {
148 git_vector_free(&files
);
149 git_attr_path__free(&path
);
156 int git_attr_foreach(
157 git_repository
*repo
,
159 const char *pathname
,
160 int (*callback
)(const char *name
, const char *value
, void *payload
),
165 git_vector files
= GIT_VECTOR_INIT
;
169 git_attr_assignment
*assign
;
170 git_strmap
*seen
= NULL
;
172 if (git_attr_path__init(&path
, pathname
, git_repository_workdir(repo
)) < 0)
175 if ((error
= collect_attr_files(repo
, flags
, pathname
, &files
)) < 0)
178 seen
= git_strmap_alloc();
179 GITERR_CHECK_ALLOC(seen
);
181 git_vector_foreach(&files
, i
, file
) {
183 git_attr_file__foreach_matching_rule(file
, &path
, j
, rule
) {
185 git_vector_foreach(&rule
->assigns
, k
, assign
) {
186 /* skip if higher priority assignment was already seen */
187 if (git_strmap_exists(seen
, assign
->name
))
190 git_strmap_insert(seen
, assign
->name
, assign
, error
);
194 error
= callback(assign
->name
, assign
->value
, payload
);
196 giterr_set_after_callback(error
);
204 git_strmap_free(seen
);
205 git_vector_free(&files
);
206 git_attr_path__free(&path
);
212 int git_attr_add_macro(
213 git_repository
*repo
,
218 git_attr_rule
*macro
= NULL
;
221 if (git_attr_cache__init(repo
) < 0)
224 macro
= git__calloc(1, sizeof(git_attr_rule
));
225 GITERR_CHECK_ALLOC(macro
);
227 pool
= &git_repository_attr_cache(repo
)->pool
;
229 macro
->match
.pattern
= git_pool_strdup(pool
, name
);
230 GITERR_CHECK_ALLOC(macro
->match
.pattern
);
232 macro
->match
.length
= strlen(macro
->match
.pattern
);
233 macro
->match
.flags
= GIT_ATTR_FNMATCH_MACRO
;
235 error
= git_attr_assignment__parse(repo
, pool
, ¯o
->assigns
, &values
);
238 error
= git_attr_cache__insert_macro(repo
, macro
);
241 git_attr_rule__free(macro
);
246 bool git_attr_cache__is_cached(
247 git_repository
*repo
, git_attr_file_source source
, const char *path
)
249 git_buf cache_key
= GIT_BUF_INIT
;
250 git_strmap
*files
= git_repository_attr_cache(repo
)->files
;
251 const char *workdir
= git_repository_workdir(repo
);
254 if (workdir
&& git__prefixcmp(path
, workdir
) == 0)
255 path
+= strlen(workdir
);
256 if (git_buf_printf(&cache_key
, "%d#%s", (int)source
, path
) < 0)
259 rval
= git_strmap_exists(files
, git_buf_cstr(&cache_key
));
261 git_buf_free(&cache_key
);
266 static int load_attr_file(
268 git_futils_filestamp
*stamp
,
269 const char *filename
)
272 git_buf content
= GIT_BUF_INIT
;
274 error
= git_futils_filestamp_check(stamp
, filename
);
278 /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
279 * we tell the caller not to reparse this file...
282 return GIT_ENOTFOUND
;
284 error
= git_futils_readbuffer(&content
, filename
);
286 /* convert error into ENOTFOUND so failed permissions / invalid
287 * file type don't actually stop the operation in progress.
289 return GIT_ENOTFOUND
;
291 /* TODO: once warnings are available, issue a warning callback */
294 *data
= git_buf_detach(&content
);
299 static int load_attr_blob_from_index(
300 const char **content
,
302 git_repository
*repo
,
303 const git_oid
*old_oid
,
309 const git_index_entry
*entry
;
311 if ((error
= git_repository_index__weakptr(&index
, repo
)) < 0 ||
312 (error
= git_index_find(&pos
, index
, relfile
)) < 0)
315 entry
= git_index_get_byindex(index
, pos
);
317 if (old_oid
&& git_oid__cmp(old_oid
, &entry
->id
) == 0)
318 return GIT_ENOTFOUND
;
320 if ((error
= git_blob_lookup(blob
, repo
, &entry
->id
)) < 0)
323 *content
= git_blob_rawcontent(*blob
);
327 static int load_attr_from_cache(
328 git_attr_file
**file
,
329 git_attr_cache
*cache
,
330 git_attr_file_source source
,
331 const char *relative_path
)
333 git_buf cache_key
= GIT_BUF_INIT
;
338 if (!cache
|| !cache
->files
)
341 if (git_buf_printf(&cache_key
, "%d#%s", (int)source
, relative_path
) < 0)
344 cache_pos
= git_strmap_lookup_index(cache
->files
, cache_key
.ptr
);
346 git_buf_free(&cache_key
);
348 if (git_strmap_valid_index(cache
->files
, cache_pos
))
349 *file
= git_strmap_value_at(cache
->files
, cache_pos
);
354 int git_attr_cache__internal_file(
355 git_repository
*repo
,
356 const char *filename
,
357 git_attr_file
**file
)
360 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
361 khiter_t cache_pos
= git_strmap_lookup_index(cache
->files
, filename
);
363 if (git_strmap_valid_index(cache
->files
, cache_pos
)) {
364 *file
= git_strmap_value_at(cache
->files
, cache_pos
);
368 if (git_attr_file__new(file
, 0, filename
, &cache
->pool
) < 0)
371 git_strmap_insert(cache
->files
, (*file
)->key
+ 2, *file
, error
);
378 int git_attr_cache__push_file(
379 git_repository
*repo
,
381 const char *filename
,
382 git_attr_file_source source
,
383 git_attr_file_parser parse
,
388 git_buf path
= GIT_BUF_INIT
;
389 const char *workdir
= git_repository_workdir(repo
);
390 const char *relfile
, *content
= NULL
;
391 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
392 git_attr_file
*file
= NULL
;
393 git_blob
*blob
= NULL
;
394 git_futils_filestamp stamp
;
396 assert(filename
&& stack
);
398 /* join base and path as needed */
399 if (base
!= NULL
&& git_path_root(filename
) < 0) {
400 if (git_buf_joinpath(&path
, base
, filename
) < 0)
406 if (workdir
&& git__prefixcmp(relfile
, workdir
) == 0)
407 relfile
+= strlen(workdir
);
410 if (load_attr_from_cache(&file
, cache
, source
, relfile
) < 0)
413 /* if not in cache, load data, parse, and cache */
415 if (source
== GIT_ATTR_FILE_FROM_FILE
) {
416 git_futils_filestamp_set(
417 &stamp
, file
? &file
->cache_data
.stamp
: NULL
);
419 error
= load_attr_file(&content
, &stamp
, filename
);
421 error
= load_attr_blob_from_index(&content
, &blob
,
422 repo
, file
? &file
->cache_data
.oid
: NULL
, relfile
);
426 /* not finding a file is not an error for this function */
427 if (error
== GIT_ENOTFOUND
) {
434 /* if we got here, we have to parse and/or reparse the file */
436 git_attr_file__clear_rules(file
);
438 error
= git_attr_file__new(&file
, source
, relfile
, &cache
->pool
);
443 if (parse
&& (error
= parse(repo
, parsedata
, content
, file
)) < 0)
446 git_strmap_insert(cache
->files
, file
->key
, file
, error
); //-V595
450 /* remember "cache buster" file signature */
452 git_oid_cpy(&file
->cache_data
.oid
, git_object_id((git_object
*)blob
));
454 git_futils_filestamp_set(&file
->cache_data
.stamp
, &stamp
);
457 /* push file onto vector if we found one*/
458 if (!error
&& file
!= NULL
)
459 error
= git_vector_insert(stack
, file
);
462 git_attr_file__free(file
);
467 git__free((void *)content
);
474 #define push_attr_file(R,S,B,F) \
475 git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
478 git_repository
*repo
;
485 int git_attr_cache__decide_sources(
486 uint32_t flags
, bool has_wd
, bool has_index
, git_attr_file_source
*srcs
)
490 switch (flags
& 0x03) {
491 case GIT_ATTR_CHECK_FILE_THEN_INDEX
:
493 srcs
[count
++] = GIT_ATTR_FILE_FROM_FILE
;
495 srcs
[count
++] = GIT_ATTR_FILE_FROM_INDEX
;
497 case GIT_ATTR_CHECK_INDEX_THEN_FILE
:
499 srcs
[count
++] = GIT_ATTR_FILE_FROM_INDEX
;
501 srcs
[count
++] = GIT_ATTR_FILE_FROM_FILE
;
503 case GIT_ATTR_CHECK_INDEX_ONLY
:
505 srcs
[count
++] = GIT_ATTR_FILE_FROM_INDEX
;
512 static int push_one_attr(void *ref
, git_buf
*path
)
514 int error
= 0, n_src
, i
;
515 attr_walk_up_info
*info
= (attr_walk_up_info
*)ref
;
516 git_attr_file_source src
[2];
518 n_src
= git_attr_cache__decide_sources(
519 info
->flags
, info
->workdir
!= NULL
, info
->index
!= NULL
, src
);
521 for (i
= 0; !error
&& i
< n_src
; ++i
)
522 error
= git_attr_cache__push_file(
523 info
->repo
, path
->ptr
, GIT_ATTR_FILE
, src
[i
],
524 git_attr_file__parse_buffer
, NULL
, info
->files
);
529 static int collect_attr_files(
530 git_repository
*repo
,
536 git_buf dir
= GIT_BUF_INIT
;
537 const char *workdir
= git_repository_workdir(repo
);
538 attr_walk_up_info info
= { NULL
};
540 if (git_attr_cache__init(repo
) < 0 ||
541 git_vector_init(files
, 4, NULL
) < 0)
544 /* Resolve path in a non-bare repo */
546 error
= git_path_find_dir(&dir
, path
, workdir
);
548 error
= git_path_dirname_r(&dir
, path
);
552 /* in precendence order highest to lowest:
553 * - $GIT_DIR/info/attributes
554 * - path components with .gitattributes
555 * - config core.attributesfile
556 * - $GIT_PREFIX/etc/gitattributes
559 error
= push_attr_file(
560 repo
, files
, git_repository_path(repo
), GIT_ATTR_FILE_INREPO
);
566 info
.workdir
= workdir
;
567 if (git_repository_index__weakptr(&info
.index
, repo
) < 0)
568 giterr_clear(); /* no error even if there is no index */
571 error
= git_path_walk_up(&dir
, workdir
, push_one_attr
, &info
);
575 if (git_repository_attr_cache(repo
)->cfg_attr_file
!= NULL
) {
576 error
= push_attr_file(
577 repo
, files
, NULL
, git_repository_attr_cache(repo
)->cfg_attr_file
);
582 if ((flags
& GIT_ATTR_CHECK_NO_SYSTEM
) == 0) {
583 error
= git_futils_find_system_file(&dir
, GIT_ATTR_FILE_SYSTEM
);
585 error
= push_attr_file(repo
, files
, NULL
, dir
.ptr
);
586 else if (error
== GIT_ENOTFOUND
) {
594 git_vector_free(files
);
600 static int attr_cache__lookup_path(
601 char **out
, git_config
*cfg
, const char *key
, const char *fallback
)
603 git_buf buf
= GIT_BUF_INIT
;
605 const git_config_entry
*entry
= NULL
;
609 if ((error
= git_config__lookup_entry(&entry
, cfg
, key
, false)) < 0)
613 const char *cfgval
= entry
->value
;
615 /* expand leading ~/ as needed */
616 if (cfgval
&& cfgval
[0] == '~' && cfgval
[1] == '/' &&
617 !git_futils_find_global_file(&buf
, &cfgval
[2]))
618 *out
= git_buf_detach(&buf
);
620 *out
= git__strdup(cfgval
);
623 else if (!git_futils_find_xdg_file(&buf
, fallback
))
624 *out
= git_buf_detach(&buf
);
631 int git_attr_cache__init(git_repository
*repo
)
634 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
637 if (cache
->initialized
)
640 /* cache config settings for attributes and ignores */
641 if (git_repository_config__weakptr(&cfg
, repo
) < 0)
644 ret
= attr_cache__lookup_path(
645 &cache
->cfg_attr_file
, cfg
, GIT_ATTR_CONFIG
, GIT_ATTR_FILE_XDG
);
649 ret
= attr_cache__lookup_path(
650 &cache
->cfg_excl_file
, cfg
, GIT_IGNORE_CONFIG
, GIT_IGNORE_FILE_XDG
);
654 /* allocate hashtable for attribute and ignore file contents */
655 if (cache
->files
== NULL
) {
656 cache
->files
= git_strmap_alloc();
657 GITERR_CHECK_ALLOC(cache
->files
);
660 /* allocate hashtable for attribute macros */
661 if (cache
->macros
== NULL
) {
662 cache
->macros
= git_strmap_alloc();
663 GITERR_CHECK_ALLOC(cache
->macros
);
666 /* allocate string pool */
667 if (git_pool_init(&cache
->pool
, 1, 0) < 0)
670 cache
->initialized
= 1;
672 /* insert default macros */
673 return git_attr_add_macro(repo
, "binary", "-diff -crlf -text");
676 void git_attr_cache_flush(
677 git_repository
*repo
)
679 git_attr_cache
*cache
;
684 cache
= git_repository_attr_cache(repo
);
686 if (cache
->files
!= NULL
) {
689 git_strmap_foreach_value(cache
->files
, file
, {
690 git_attr_file__free(file
);
693 git_strmap_free(cache
->files
);
696 if (cache
->macros
!= NULL
) {
699 git_strmap_foreach_value(cache
->macros
, rule
, {
700 git_attr_rule__free(rule
);
703 git_strmap_free(cache
->macros
);
706 git_pool_clear(&cache
->pool
);
708 git__free(cache
->cfg_attr_file
);
709 cache
->cfg_attr_file
= NULL
;
711 git__free(cache
->cfg_excl_file
);
712 cache
->cfg_excl_file
= NULL
;
714 cache
->initialized
= 0;
717 int git_attr_cache__insert_macro(git_repository
*repo
, git_attr_rule
*macro
)
719 git_strmap
*macros
= git_repository_attr_cache(repo
)->macros
;
722 /* TODO: generate warning log if (macro->assigns.length == 0) */
723 if (macro
->assigns
.length
== 0)
726 git_strmap_insert(macros
, macro
->match
.pattern
, macro
, error
);
727 return (error
< 0) ? -1 : 0;
730 git_attr_rule
*git_attr_cache__lookup_macro(
731 git_repository
*repo
, const char *name
)
733 git_strmap
*macros
= git_repository_attr_cache(repo
)->macros
;
736 pos
= git_strmap_lookup_index(macros
, name
);
738 if (!git_strmap_valid_index(macros
, pos
))
741 return (git_attr_rule
*)git_strmap_value_at(macros
, pos
);