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"
11 #include "attr_file.h"
17 GIT_INLINE(int) attr_cache_lock(git_attr_cache
*cache
)
19 GIT_UNUSED(cache
); /* avoid warning if threading is off */
21 if (git_mutex_lock(&cache
->lock
) < 0) {
22 git_error_set(GIT_ERROR_OS
, "unable to get attr cache lock");
28 GIT_INLINE(void) attr_cache_unlock(git_attr_cache
*cache
)
30 GIT_UNUSED(cache
); /* avoid warning if threading is off */
31 git_mutex_unlock(&cache
->lock
);
34 GIT_INLINE(git_attr_file_entry
*) attr_cache_lookup_entry(
35 git_attr_cache
*cache
, const char *path
)
37 return git_strmap_get(cache
->files
, path
);
40 int git_attr_cache__alloc_file_entry(
41 git_attr_file_entry
**out
,
47 git_str fullpath_str
= GIT_STR_INIT
;
48 size_t baselen
= 0, pathlen
= strlen(path
);
49 size_t cachesize
= sizeof(git_attr_file_entry
) + pathlen
+ 1;
50 git_attr_file_entry
*ce
;
52 if (base
!= NULL
&& git_fs_path_root(path
) < 0) {
53 baselen
= strlen(base
);
56 if (baselen
&& base
[baselen
- 1] != '/')
60 ce
= git_pool_mallocz(pool
, cachesize
);
61 GIT_ERROR_CHECK_ALLOC(ce
);
64 memcpy(ce
->fullpath
, base
, baselen
);
66 if (base
[baselen
- 1] != '/')
67 ce
->fullpath
[baselen
++] = '/';
69 memcpy(&ce
->fullpath
[baselen
], path
, pathlen
);
71 fullpath_str
.ptr
= ce
->fullpath
;
72 fullpath_str
.size
= pathlen
+ baselen
;
74 if (git_path_validate_str_length(repo
, &fullpath_str
) < 0)
77 ce
->path
= &ce
->fullpath
[baselen
];
83 /* call with attrcache locked */
84 static int attr_cache_make_entry(
85 git_attr_file_entry
**out
, git_repository
*repo
, const char *path
)
87 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
88 git_attr_file_entry
*entry
= NULL
;
91 if ((error
= git_attr_cache__alloc_file_entry(&entry
, repo
,
92 git_repository_workdir(repo
), path
, &cache
->pool
)) < 0)
95 if ((error
= git_strmap_set(cache
->files
, entry
->path
, entry
)) < 0)
102 /* insert entry or replace existing if we raced with another thread */
103 static int attr_cache_upsert(git_attr_cache
*cache
, git_attr_file
*file
)
105 git_attr_file_entry
*entry
;
108 if (attr_cache_lock(cache
) < 0)
111 entry
= attr_cache_lookup_entry(cache
, file
->entry
->path
);
113 GIT_REFCOUNT_OWN(file
, entry
);
114 GIT_REFCOUNT_INC(file
);
117 * Replace the existing value if another thread has
118 * created it in the meantime.
120 old
= git_atomic_swap(entry
->file
[file
->source
.type
], file
);
123 GIT_REFCOUNT_OWN(old
, NULL
);
124 git_attr_file__free(old
);
127 attr_cache_unlock(cache
);
131 static int attr_cache_remove(git_attr_cache
*cache
, git_attr_file
*file
)
134 git_attr_file_entry
*entry
;
135 git_attr_file
*oldfile
= NULL
;
140 if ((error
= attr_cache_lock(cache
)) < 0)
143 if ((entry
= attr_cache_lookup_entry(cache
, file
->entry
->path
)) != NULL
)
144 oldfile
= git_atomic_compare_and_swap(&entry
->file
[file
->source
.type
], file
, NULL
);
146 attr_cache_unlock(cache
);
148 if (oldfile
== file
) {
149 GIT_REFCOUNT_OWN(file
, NULL
);
150 git_attr_file__free(file
);
156 /* Look up cache entry and file.
157 * - If entry is not present, create it while the cache is locked.
158 * - If file is present, increment refcount before returning it, so the
159 * cache can be unlocked and it won't go away.
161 static int attr_cache_lookup(
162 git_attr_file
**out_file
,
163 git_attr_file_entry
**out_entry
,
164 git_repository
*repo
,
165 git_attr_session
*attr_session
,
166 git_attr_file_source
*source
)
169 git_str path
= GIT_STR_INIT
;
170 const char *wd
= git_repository_workdir(repo
);
171 const char *filename
;
172 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
173 git_attr_file_entry
*entry
= NULL
;
174 git_attr_file
*file
= NULL
;
176 /* join base and path as needed */
177 if (source
->base
!= NULL
&& git_fs_path_root(source
->filename
) < 0) {
178 git_str
*p
= attr_session
? &attr_session
->tmp
: &path
;
180 if (git_str_joinpath(p
, source
->base
, source
->filename
) < 0 ||
181 git_path_validate_str_length(repo
, p
) < 0)
186 filename
= source
->filename
;
189 if (wd
&& !git__prefixcmp(filename
, wd
))
190 filename
+= strlen(wd
);
192 /* check cache for existing entry */
193 if ((error
= attr_cache_lock(cache
)) < 0)
196 entry
= attr_cache_lookup_entry(cache
, filename
);
199 error
= attr_cache_make_entry(&entry
, repo
, filename
);
200 } else if (entry
->file
[source
->type
] != NULL
) {
201 file
= entry
->file
[source
->type
];
202 GIT_REFCOUNT_INC(file
);
205 attr_cache_unlock(cache
);
211 git_str_dispose(&path
);
215 int git_attr_cache__get(
217 git_repository
*repo
,
218 git_attr_session
*attr_session
,
219 git_attr_file_source
*source
,
220 git_attr_file_parser parser
,
224 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
225 git_attr_file_entry
*entry
= NULL
;
226 git_attr_file
*file
= NULL
, *updated
= NULL
;
228 if ((error
= attr_cache_lookup(&file
, &entry
, repo
, attr_session
, source
)) < 0)
231 /* load file if we don't have one or if existing one is out of date */
233 (error
= git_attr_file__out_of_date(repo
, attr_session
, file
, source
)) > 0)
234 error
= git_attr_file__load(&updated
, repo
, attr_session
,
235 entry
, source
, parser
,
238 /* if we loaded the file, insert into and/or update cache */
240 if ((error
= attr_cache_upsert(cache
, updated
)) < 0) {
241 git_attr_file__free(updated
);
243 git_attr_file__free(file
); /* offset incref from lookup */
248 /* if file could not be loaded */
250 /* remove existing entry */
252 attr_cache_remove(cache
, file
);
253 git_attr_file__free(file
); /* offset incref from lookup */
256 /* no error if file simply doesn't exist */
257 if (error
== GIT_ENOTFOUND
) {
267 bool git_attr_cache__is_cached(
268 git_repository
*repo
,
269 git_attr_file_source_t source_type
,
270 const char *filename
)
272 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
273 git_attr_file_entry
*entry
;
276 if (!cache
|| !(files
= cache
->files
))
279 if ((entry
= git_strmap_get(files
, filename
)) == NULL
)
282 return entry
&& (entry
->file
[source_type
] != NULL
);
286 static int attr_cache__lookup_path(
287 char **out
, git_config
*cfg
, const char *key
, const char *fallback
)
289 git_str buf
= GIT_STR_INIT
;
291 git_config_entry
*entry
= NULL
;
295 if ((error
= git_config__lookup_entry(&entry
, cfg
, key
, false)) < 0)
299 const char *cfgval
= entry
->value
;
301 /* expand leading ~/ as needed */
302 if (cfgval
&& cfgval
[0] == '~' && cfgval
[1] == '/') {
303 if (! (error
= git_sysdir_expand_global_file(&buf
, &cfgval
[2])))
304 *out
= git_str_detach(&buf
);
306 *out
= git__strdup(cfgval
);
309 else if (!git_sysdir_find_xdg_file(&buf
, fallback
)) {
310 *out
= git_str_detach(&buf
);
313 git_config_entry_free(entry
);
314 git_str_dispose(&buf
);
319 static void attr_cache__free(git_attr_cache
*cache
)
326 unlock
= (attr_cache_lock(cache
) == 0);
328 if (cache
->files
!= NULL
) {
329 git_attr_file_entry
*entry
;
333 git_strmap_foreach_value(cache
->files
, entry
, {
334 for (i
= 0; i
< GIT_ATTR_FILE_NUM_SOURCES
; ++i
) {
335 if ((file
= git_atomic_swap(entry
->file
[i
], NULL
)) != NULL
) {
336 GIT_REFCOUNT_OWN(file
, NULL
);
337 git_attr_file__free(file
);
341 git_strmap_free(cache
->files
);
344 if (cache
->macros
!= NULL
) {
347 git_strmap_foreach_value(cache
->macros
, rule
, {
348 git_attr_rule__free(rule
);
350 git_strmap_free(cache
->macros
);
353 git_pool_clear(&cache
->pool
);
355 git__free(cache
->cfg_attr_file
);
356 cache
->cfg_attr_file
= NULL
;
358 git__free(cache
->cfg_excl_file
);
359 cache
->cfg_excl_file
= NULL
;
362 attr_cache_unlock(cache
);
363 git_mutex_free(&cache
->lock
);
368 int git_attr_cache__init(git_repository
*repo
)
371 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
372 git_config
*cfg
= NULL
;
377 cache
= git__calloc(1, sizeof(git_attr_cache
));
378 GIT_ERROR_CHECK_ALLOC(cache
);
381 if (git_mutex_init(&cache
->lock
) < 0) {
382 git_error_set(GIT_ERROR_OS
, "unable to initialize lock for attr cache");
387 if ((ret
= git_repository_config_snapshot(&cfg
, repo
)) < 0)
390 /* cache config settings for attributes and ignores */
391 ret
= attr_cache__lookup_path(
392 &cache
->cfg_attr_file
, cfg
, GIT_ATTR_CONFIG
, GIT_ATTR_FILE_XDG
);
396 ret
= attr_cache__lookup_path(
397 &cache
->cfg_excl_file
, cfg
, GIT_IGNORE_CONFIG
, GIT_IGNORE_FILE_XDG
);
401 /* allocate hashtable for attribute and ignore file contents,
402 * hashtable for attribute macros, and string pool
404 if ((ret
= git_strmap_new(&cache
->files
)) < 0 ||
405 (ret
= git_strmap_new(&cache
->macros
)) < 0 ||
406 (ret
= git_pool_init(&cache
->pool
, 1)) < 0)
409 if (git_atomic_compare_and_swap(&repo
->attrcache
, NULL
, cache
) != NULL
)
410 goto cancel
; /* raced with another thread, free this but no error */
412 git_config_free(cfg
);
414 /* insert default macros */
415 return git_attr_add_macro(repo
, "binary", "-diff -merge -text -crlf");
418 attr_cache__free(cache
);
419 git_config_free(cfg
);
423 int git_attr_cache_flush(git_repository
*repo
)
425 git_attr_cache
*cache
;
427 /* this could be done less expensively, but for now, we'll just free
428 * the entire attrcache and let the next use reinitialize it...
430 if (repo
&& (cache
= git_atomic_swap(repo
->attrcache
, NULL
)) != NULL
)
431 attr_cache__free(cache
);
436 int git_attr_cache__insert_macro(git_repository
*repo
, git_attr_rule
*macro
)
438 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
439 git_attr_rule
*preexisting
;
444 * Callers assume that if we return success, that the
445 * macro will have been adopted by the attributes cache.
446 * Thus, we have to free the macro here if it's not being
447 * added to the cache.
449 * TODO: generate warning log if (macro->assigns.length == 0)
451 if (macro
->assigns
.length
== 0) {
452 git_attr_rule__free(macro
);
456 if ((error
= attr_cache_lock(cache
)) < 0)
460 if ((preexisting
= git_strmap_get(cache
->macros
, macro
->match
.pattern
)) != NULL
)
461 git_attr_rule__free(preexisting
);
463 if ((error
= git_strmap_set(cache
->macros
, macro
->match
.pattern
, macro
)) < 0)
468 attr_cache_unlock(cache
);
472 git_attr_rule
*git_attr_cache__lookup_macro(
473 git_repository
*repo
, const char *name
)
475 git_strmap
*macros
= git_repository_attr_cache(repo
)->macros
;
477 return git_strmap_get(macros
, name
);