2 #include "repository.h"
10 GIT_INLINE(int) attr_cache_lock(git_attr_cache
*cache
)
12 GIT_UNUSED(cache
); /* avoid warning if threading is off */
14 if (git_mutex_lock(&cache
->lock
) < 0) {
15 giterr_set(GITERR_OS
, "Unable to get attr cache lock");
21 GIT_INLINE(void) attr_cache_unlock(git_attr_cache
*cache
)
23 GIT_UNUSED(cache
); /* avoid warning if threading is off */
24 git_mutex_unlock(&cache
->lock
);
27 GIT_INLINE(git_attr_file_entry
*) attr_cache_lookup_entry(
28 git_attr_cache
*cache
, const char *path
)
30 khiter_t pos
= git_strmap_lookup_index(cache
->files
, path
);
32 if (git_strmap_valid_index(cache
->files
, pos
))
33 return git_strmap_value_at(cache
->files
, pos
);
38 int git_attr_cache__alloc_file_entry(
39 git_attr_file_entry
**out
,
44 size_t baselen
= 0, pathlen
= strlen(path
);
45 size_t cachesize
= sizeof(git_attr_file_entry
) + pathlen
+ 1;
46 git_attr_file_entry
*ce
;
48 if (base
!= NULL
&& git_path_root(path
) < 0) {
49 baselen
= strlen(base
);
52 if (baselen
&& base
[baselen
- 1] != '/')
56 ce
= git_pool_mallocz(pool
, cachesize
);
57 GITERR_CHECK_ALLOC(ce
);
60 memcpy(ce
->fullpath
, base
, baselen
);
62 if (base
[baselen
- 1] != '/')
63 ce
->fullpath
[baselen
++] = '/';
65 memcpy(&ce
->fullpath
[baselen
], path
, pathlen
);
67 ce
->path
= &ce
->fullpath
[baselen
];
73 /* call with attrcache locked */
74 static int attr_cache_make_entry(
75 git_attr_file_entry
**out
, git_repository
*repo
, const char *path
)
78 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
79 git_attr_file_entry
*entry
= NULL
;
81 error
= git_attr_cache__alloc_file_entry(
82 &entry
, git_repository_workdir(repo
), path
, &cache
->pool
);
85 git_strmap_insert(cache
->files
, entry
->path
, entry
, error
);
94 /* insert entry or replace existing if we raced with another thread */
95 static int attr_cache_upsert(git_attr_cache
*cache
, git_attr_file
*file
)
97 git_attr_file_entry
*entry
;
100 if (attr_cache_lock(cache
) < 0)
103 entry
= attr_cache_lookup_entry(cache
, file
->entry
->path
);
105 GIT_REFCOUNT_OWN(file
, entry
);
106 GIT_REFCOUNT_INC(file
);
108 old
= git__compare_and_swap(
109 &entry
->file
[file
->source
], entry
->file
[file
->source
], file
);
112 GIT_REFCOUNT_OWN(old
, NULL
);
113 git_attr_file__free(old
);
116 attr_cache_unlock(cache
);
120 static int attr_cache_remove(git_attr_cache
*cache
, git_attr_file
*file
)
123 git_attr_file_entry
*entry
;
127 if ((error
= attr_cache_lock(cache
)) < 0)
130 if ((entry
= attr_cache_lookup_entry(cache
, file
->entry
->path
)) != NULL
)
131 file
= git__compare_and_swap(&entry
->file
[file
->source
], file
, NULL
);
133 attr_cache_unlock(cache
);
136 GIT_REFCOUNT_OWN(file
, NULL
);
137 git_attr_file__free(file
);
143 /* Look up cache entry and file.
144 * - If entry is not present, create it while the cache is locked.
145 * - If file is present, increment refcount before returning it, so the
146 * cache can be unlocked and it won't go away.
148 static int attr_cache_lookup(
149 git_attr_file
**out_file
,
150 git_attr_file_entry
**out_entry
,
151 git_repository
*repo
,
152 git_attr_file_source source
,
154 const char *filename
)
157 git_buf path
= GIT_BUF_INIT
;
158 const char *wd
= git_repository_workdir(repo
), *relfile
;
159 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
160 git_attr_file_entry
*entry
= NULL
;
161 git_attr_file
*file
= NULL
;
163 /* join base and path as needed */
164 if (base
!= NULL
&& git_path_root(filename
) < 0) {
165 if (git_buf_joinpath(&path
, base
, filename
) < 0)
171 if (wd
&& !git__prefixcmp(relfile
, wd
))
172 relfile
+= strlen(wd
);
174 /* check cache for existing entry */
175 if ((error
= attr_cache_lock(cache
)) < 0)
178 entry
= attr_cache_lookup_entry(cache
, relfile
);
180 if ((error
= attr_cache_make_entry(&entry
, repo
, relfile
)) < 0)
182 } else if (entry
->file
[source
] != NULL
) {
183 file
= entry
->file
[source
];
184 GIT_REFCOUNT_INC(file
);
187 attr_cache_unlock(cache
);
197 int git_attr_cache__get(
199 git_repository
*repo
,
200 git_attr_file_source source
,
202 const char *filename
,
203 git_attr_file_parser parser
)
206 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
207 git_attr_file_entry
*entry
= NULL
;
208 git_attr_file
*file
= NULL
, *updated
= NULL
;
210 if ((error
= attr_cache_lookup(
211 &file
, &entry
, repo
, source
, base
, filename
)) < 0)
214 /* load file if we don't have one or if existing one is out of date */
215 if (!file
|| (error
= git_attr_file__out_of_date(repo
, file
)) > 0)
216 error
= git_attr_file__load(&updated
, repo
, entry
, source
, parser
);
218 /* if we loaded the file, insert into and/or update cache */
220 if ((error
= attr_cache_upsert(cache
, updated
)) < 0)
221 git_attr_file__free(updated
);
223 git_attr_file__free(file
); /* offset incref from lookup */
228 /* if file could not be loaded */
230 /* remove existing entry */
232 attr_cache_remove(cache
, file
);
233 git_attr_file__free(file
); /* offset incref from lookup */
236 /* no error if file simply doesn't exist */
237 if (error
== GIT_ENOTFOUND
) {
247 bool git_attr_cache__is_cached(
248 git_repository
*repo
,
249 git_attr_file_source source
,
250 const char *filename
)
252 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
255 git_attr_file_entry
*entry
;
257 if (!(cache
= git_repository_attr_cache(repo
)) ||
258 !(files
= cache
->files
))
261 pos
= git_strmap_lookup_index(files
, filename
);
262 if (!git_strmap_valid_index(files
, pos
))
265 entry
= git_strmap_value_at(files
, pos
);
267 return entry
&& (entry
->file
[source
] != NULL
);
271 static int attr_cache__lookup_path(
272 char **out
, git_config
*cfg
, const char *key
, const char *fallback
)
274 git_buf buf
= GIT_BUF_INIT
;
276 const git_config_entry
*entry
= NULL
;
280 if ((error
= git_config__lookup_entry(&entry
, cfg
, key
, false)) < 0)
284 const char *cfgval
= entry
->value
;
286 /* expand leading ~/ as needed */
287 if (cfgval
&& cfgval
[0] == '~' && cfgval
[1] == '/' &&
288 !git_sysdir_find_global_file(&buf
, &cfgval
[2]))
289 *out
= git_buf_detach(&buf
);
291 *out
= git__strdup(cfgval
);
293 else if (!git_sysdir_find_xdg_file(&buf
, fallback
))
294 *out
= git_buf_detach(&buf
);
301 static void attr_cache__free(git_attr_cache
*cache
)
308 unlock
= (git_mutex_lock(&cache
->lock
) == 0);
310 if (cache
->files
!= NULL
) {
311 git_attr_file_entry
*entry
;
315 git_strmap_foreach_value(cache
->files
, entry
, {
316 for (i
= 0; i
< GIT_ATTR_FILE_NUM_SOURCES
; ++i
) {
317 if ((file
= git__swap(entry
->file
[i
], NULL
)) != NULL
) {
318 GIT_REFCOUNT_OWN(file
, NULL
);
319 git_attr_file__free(file
);
323 git_strmap_free(cache
->files
);
326 if (cache
->macros
!= NULL
) {
329 git_strmap_foreach_value(cache
->macros
, rule
, {
330 git_attr_rule__free(rule
);
332 git_strmap_free(cache
->macros
);
335 git_pool_clear(&cache
->pool
);
337 git__free(cache
->cfg_attr_file
);
338 cache
->cfg_attr_file
= NULL
;
340 git__free(cache
->cfg_excl_file
);
341 cache
->cfg_excl_file
= NULL
;
344 git_mutex_unlock(&cache
->lock
);
345 git_mutex_free(&cache
->lock
);
350 int git_attr_cache__do_init(git_repository
*repo
)
353 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
359 if ((ret
= git_repository_config__weakptr(&cfg
, repo
)) < 0)
362 cache
= git__calloc(1, sizeof(git_attr_cache
));
363 GITERR_CHECK_ALLOC(cache
);
366 if (git_mutex_init(&cache
->lock
) < 0) {
367 giterr_set(GITERR_OS
, "Unable to initialize lock for attr cache");
372 /* cache config settings for attributes and ignores */
373 ret
= attr_cache__lookup_path(
374 &cache
->cfg_attr_file
, cfg
, GIT_ATTR_CONFIG
, GIT_ATTR_FILE_XDG
);
378 ret
= attr_cache__lookup_path(
379 &cache
->cfg_excl_file
, cfg
, GIT_IGNORE_CONFIG
, GIT_IGNORE_FILE_XDG
);
383 /* allocate hashtable for attribute and ignore file contents,
384 * hashtable for attribute macros, and string pool
386 if ((ret
= git_strmap_alloc(&cache
->files
)) < 0 ||
387 (ret
= git_strmap_alloc(&cache
->macros
)) < 0 ||
388 (ret
= git_pool_init(&cache
->pool
, 1, 0)) < 0)
391 cache
= git__compare_and_swap(&repo
->attrcache
, NULL
, cache
);
393 goto cancel
; /* raced with another thread, free this but no error */
395 /* insert default macros */
396 return git_attr_add_macro(repo
, "binary", "-diff -crlf -text");
399 attr_cache__free(cache
);
403 void git_attr_cache_flush(git_repository
*repo
)
405 git_attr_cache
*cache
;
407 /* this could be done less expensively, but for now, we'll just free
408 * the entire attrcache and let the next use reinitialize it...
410 if (repo
&& (cache
= git__swap(repo
->attrcache
, NULL
)) != NULL
)
411 attr_cache__free(cache
);
414 int git_attr_cache__insert_macro(git_repository
*repo
, git_attr_rule
*macro
)
416 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
417 git_strmap
*macros
= cache
->macros
;
420 /* TODO: generate warning log if (macro->assigns.length == 0) */
421 if (macro
->assigns
.length
== 0)
424 if (git_mutex_lock(&cache
->lock
) < 0) {
425 giterr_set(GITERR_OS
, "Unable to get attr cache lock");
428 git_strmap_insert(macros
, macro
->match
.pattern
, macro
, error
);
429 git_mutex_unlock(&cache
->lock
);
432 return (error
< 0) ? -1 : 0;
435 git_attr_rule
*git_attr_cache__lookup_macro(
436 git_repository
*repo
, const char *name
)
438 git_strmap
*macros
= git_repository_attr_cache(repo
)->macros
;
441 pos
= git_strmap_lookup_index(macros
, name
);
443 if (!git_strmap_valid_index(macros
, pos
))
446 return (git_attr_rule
*)git_strmap_value_at(macros
, pos
);