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
, (uint32_t)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_session
*attr_session
,
153 git_attr_file_source source
,
155 const char *filename
)
158 git_buf path
= GIT_BUF_INIT
;
159 const char *wd
= git_repository_workdir(repo
), *relfile
;
160 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
161 git_attr_file_entry
*entry
= NULL
;
162 git_attr_file
*file
= NULL
;
164 /* join base and path as needed */
165 if (base
!= NULL
&& git_path_root(filename
) < 0) {
166 git_buf
*p
= attr_session
? &attr_session
->tmp
: &path
;
168 if (git_buf_joinpath(p
, base
, filename
) < 0)
175 if (wd
&& !git__prefixcmp(relfile
, wd
))
176 relfile
+= strlen(wd
);
178 /* check cache for existing entry */
179 if ((error
= attr_cache_lock(cache
)) < 0)
182 entry
= attr_cache_lookup_entry(cache
, relfile
);
184 error
= attr_cache_make_entry(&entry
, repo
, relfile
);
185 else if (entry
->file
[source
] != NULL
) {
186 file
= entry
->file
[source
];
187 GIT_REFCOUNT_INC(file
);
190 attr_cache_unlock(cache
);
200 int git_attr_cache__get(
202 git_repository
*repo
,
203 git_attr_session
*attr_session
,
204 git_attr_file_source source
,
206 const char *filename
,
207 git_attr_file_parser parser
)
210 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
211 git_attr_file_entry
*entry
= NULL
;
212 git_attr_file
*file
= NULL
, *updated
= NULL
;
214 if ((error
= attr_cache_lookup(
215 &file
, &entry
, repo
, attr_session
, source
, base
, filename
)) < 0)
218 /* load file if we don't have one or if existing one is out of date */
219 if (!file
|| (error
= git_attr_file__out_of_date(repo
, attr_session
, file
)) > 0)
220 error
= git_attr_file__load(&updated
, repo
, attr_session
, entry
, source
, parser
);
222 /* if we loaded the file, insert into and/or update cache */
224 if ((error
= attr_cache_upsert(cache
, updated
)) < 0)
225 git_attr_file__free(updated
);
227 git_attr_file__free(file
); /* offset incref from lookup */
232 /* if file could not be loaded */
234 /* remove existing entry */
236 attr_cache_remove(cache
, file
);
237 git_attr_file__free(file
); /* offset incref from lookup */
240 /* no error if file simply doesn't exist */
241 if (error
== GIT_ENOTFOUND
) {
251 bool git_attr_cache__is_cached(
252 git_repository
*repo
,
253 git_attr_file_source source
,
254 const char *filename
)
256 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
259 git_attr_file_entry
*entry
;
261 if (!cache
|| !(files
= cache
->files
))
264 pos
= git_strmap_lookup_index(files
, filename
);
265 if (!git_strmap_valid_index(files
, pos
))
268 entry
= git_strmap_value_at(files
, pos
);
270 return entry
&& (entry
->file
[source
] != NULL
);
274 static int attr_cache__lookup_path(
275 char **out
, git_config
*cfg
, const char *key
, const char *fallback
)
277 git_buf buf
= GIT_BUF_INIT
;
279 const git_config_entry
*entry
= NULL
;
283 if ((error
= git_config__lookup_entry(&entry
, cfg
, key
, false)) < 0)
287 const char *cfgval
= entry
->value
;
289 /* expand leading ~/ as needed */
290 if (cfgval
&& cfgval
[0] == '~' && cfgval
[1] == '/' &&
291 !git_sysdir_find_global_file(&buf
, &cfgval
[2]))
292 *out
= git_buf_detach(&buf
);
294 *out
= git__strdup(cfgval
);
296 else if (!git_sysdir_find_xdg_file(&buf
, fallback
))
297 *out
= git_buf_detach(&buf
);
304 static void attr_cache__free(git_attr_cache
*cache
)
311 unlock
= (git_mutex_lock(&cache
->lock
) == 0);
313 if (cache
->files
!= NULL
) {
314 git_attr_file_entry
*entry
;
318 git_strmap_foreach_value(cache
->files
, entry
, {
319 for (i
= 0; i
< GIT_ATTR_FILE_NUM_SOURCES
; ++i
) {
320 if ((file
= git__swap(entry
->file
[i
], NULL
)) != NULL
) {
321 GIT_REFCOUNT_OWN(file
, NULL
);
322 git_attr_file__free(file
);
326 git_strmap_free(cache
->files
);
329 if (cache
->macros
!= NULL
) {
332 git_strmap_foreach_value(cache
->macros
, rule
, {
333 git_attr_rule__free(rule
);
335 git_strmap_free(cache
->macros
);
338 git_pool_clear(&cache
->pool
);
340 git__free(cache
->cfg_attr_file
);
341 cache
->cfg_attr_file
= NULL
;
343 git__free(cache
->cfg_excl_file
);
344 cache
->cfg_excl_file
= NULL
;
347 git_mutex_unlock(&cache
->lock
);
348 git_mutex_free(&cache
->lock
);
353 int git_attr_cache__do_init(git_repository
*repo
)
356 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
357 git_config
*cfg
= NULL
;
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 if ((ret
= git_repository_config_snapshot(&cfg
, repo
)) < 0)
375 /* cache config settings for attributes and ignores */
376 ret
= attr_cache__lookup_path(
377 &cache
->cfg_attr_file
, cfg
, GIT_ATTR_CONFIG
, GIT_ATTR_FILE_XDG
);
381 ret
= attr_cache__lookup_path(
382 &cache
->cfg_excl_file
, cfg
, GIT_IGNORE_CONFIG
, GIT_IGNORE_FILE_XDG
);
386 /* allocate hashtable for attribute and ignore file contents,
387 * hashtable for attribute macros, and string pool
389 if ((ret
= git_strmap_alloc(&cache
->files
)) < 0 ||
390 (ret
= git_strmap_alloc(&cache
->macros
)) < 0 ||
391 (ret
= git_pool_init(&cache
->pool
, 1, 0)) < 0)
394 cache
= git__compare_and_swap(&repo
->attrcache
, NULL
, cache
);
396 goto cancel
; /* raced with another thread, free this but no error */
398 git_config_free(cfg
);
400 /* insert default macros */
401 return git_attr_add_macro(repo
, "binary", "-diff -crlf -text");
404 attr_cache__free(cache
);
405 git_config_free(cfg
);
409 void git_attr_cache_flush(git_repository
*repo
)
411 git_attr_cache
*cache
;
413 /* this could be done less expensively, but for now, we'll just free
414 * the entire attrcache and let the next use reinitialize it...
416 if (repo
&& (cache
= git__swap(repo
->attrcache
, NULL
)) != NULL
)
417 attr_cache__free(cache
);
420 int git_attr_cache__insert_macro(git_repository
*repo
, git_attr_rule
*macro
)
422 git_attr_cache
*cache
= git_repository_attr_cache(repo
);
423 git_strmap
*macros
= cache
->macros
;
426 /* TODO: generate warning log if (macro->assigns.length == 0) */
427 if (macro
->assigns
.length
== 0)
430 if (git_mutex_lock(&cache
->lock
) < 0) {
431 giterr_set(GITERR_OS
, "Unable to get attr cache lock");
434 git_strmap_insert(macros
, macro
->match
.pattern
, macro
, error
);
435 git_mutex_unlock(&cache
->lock
);
438 return (error
< 0) ? -1 : 0;
441 git_attr_rule
*git_attr_cache__lookup_macro(
442 git_repository
*repo
, const char *name
)
444 git_strmap
*macros
= git_repository_attr_cache(repo
)->macros
;
447 pos
= git_strmap_lookup_index(macros
, name
);
449 if (!git_strmap_valid_index(macros
, pos
))
452 return (git_attr_rule
*)git_strmap_value_at(macros
, pos
);