File Coverage

deps/libgit2/src/libgit2/attrcache.c
Criterion Covered Total %
statement 160 222 72.0
branch 75 142 52.8
condition n/a
subroutine n/a
pod n/a
total 235 364 64.5


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "attrcache.h"
9              
10             #include "repository.h"
11             #include "attr_file.h"
12             #include "config.h"
13             #include "sysdir.h"
14             #include "ignore.h"
15             #include "path.h"
16              
17 6874           GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
18             {
19 6874           GIT_UNUSED(cache); /* avoid warning if threading is off */
20              
21 6874 50         if (git_mutex_lock(&cache->lock) < 0) {
22 0           git_error_set(GIT_ERROR_OS, "unable to get attr cache lock");
23 0           return -1;
24             }
25 6874           return 0;
26             }
27              
28 6874           GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
29             {
30 6874           GIT_UNUSED(cache); /* avoid warning if threading is off */
31 6874           git_mutex_unlock(&cache->lock);
32 6874           }
33              
34 6842           GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
35             git_attr_cache *cache, const char *path)
36             {
37 6842           return git_strmap_get(cache->files, path);
38             }
39              
40 101           int git_attr_cache__alloc_file_entry(
41             git_attr_file_entry **out,
42             git_repository *repo,
43             const char *base,
44             const char *path,
45             git_pool *pool)
46             {
47 101           git_str fullpath_str = GIT_STR_INIT;
48 101           size_t baselen = 0, pathlen = strlen(path);
49 101           size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
50             git_attr_file_entry *ce;
51              
52 101 50         if (base != NULL && git_fs_path_root(path) < 0) {
    100          
53 97           baselen = strlen(base);
54 97           cachesize += baselen;
55              
56 97 50         if (baselen && base[baselen - 1] != '/')
    50          
57 0           cachesize++;
58             }
59              
60 101           ce = git_pool_mallocz(pool, cachesize);
61 101 50         GIT_ERROR_CHECK_ALLOC(ce);
62              
63 101 100         if (baselen) {
64 97           memcpy(ce->fullpath, base, baselen);
65              
66 97 50         if (base[baselen - 1] != '/')
67 0           ce->fullpath[baselen++] = '/';
68             }
69 101           memcpy(&ce->fullpath[baselen], path, pathlen);
70              
71 101           fullpath_str.ptr = ce->fullpath;
72 101           fullpath_str.size = pathlen + baselen;
73              
74 101 50         if (git_path_validate_str_length(repo, &fullpath_str) < 0)
75 0           return -1;
76              
77 101           ce->path = &ce->fullpath[baselen];
78 101           *out = ce;
79              
80 101           return 0;
81             }
82              
83             /* call with attrcache locked */
84 101           static int attr_cache_make_entry(
85             git_attr_file_entry **out, git_repository *repo, const char *path)
86             {
87 101           git_attr_cache *cache = git_repository_attr_cache(repo);
88 101           git_attr_file_entry *entry = NULL;
89             int error;
90              
91 101 50         if ((error = git_attr_cache__alloc_file_entry(&entry, repo,
92             git_repository_workdir(repo), path, &cache->pool)) < 0)
93 0           return error;
94              
95 101 50         if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0)
96 0           return error;
97              
98 101           *out = entry;
99 101           return error;
100             }
101              
102             /* insert entry or replace existing if we raced with another thread */
103 2426           static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
104             {
105             git_attr_file_entry *entry;
106             git_attr_file *old;
107              
108 2426 50         if (attr_cache_lock(cache) < 0)
109 0           return -1;
110              
111 2426           entry = attr_cache_lookup_entry(cache, file->entry->path);
112              
113 2426           GIT_REFCOUNT_OWN(file, entry);
114 2426           GIT_REFCOUNT_INC(file);
115              
116             /*
117             * Replace the existing value if another thread has
118             * created it in the meantime.
119             */
120 2426           old = git_atomic_swap(entry->file[file->source.type], file);
121              
122 2426 100         if (old) {
123 2325           GIT_REFCOUNT_OWN(old, NULL);
124 2325           git_attr_file__free(old);
125             }
126              
127 2426           attr_cache_unlock(cache);
128 2426           return 0;
129             }
130              
131 0           static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
132             {
133 0           int error = 0;
134             git_attr_file_entry *entry;
135 0           git_attr_file *oldfile = NULL;
136              
137 0 0         if (!file)
138 0           return 0;
139              
140 0 0         if ((error = attr_cache_lock(cache)) < 0)
141 0           return error;
142              
143 0 0         if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
144 0           oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL);
145              
146 0           attr_cache_unlock(cache);
147              
148 0 0         if (oldfile == file) {
149 0           GIT_REFCOUNT_OWN(file, NULL);
150 0           git_attr_file__free(file);
151             }
152              
153 0           return error;
154             }
155              
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.
160             */
161 4416           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)
167             {
168 4416           int error = 0;
169 4416           git_str path = GIT_STR_INIT;
170 4416           const char *wd = git_repository_workdir(repo);
171             const char *filename;
172 4416           git_attr_cache *cache = git_repository_attr_cache(repo);
173 4416           git_attr_file_entry *entry = NULL;
174 4416           git_attr_file *file = NULL;
175              
176             /* join base and path as needed */
177 8131 100         if (source->base != NULL && git_fs_path_root(source->filename) < 0) {
    50          
178 3715 100         git_str *p = attr_session ? &attr_session->tmp : &path;
179              
180 7430           if (git_str_joinpath(p, source->base, source->filename) < 0 ||
181 3715           git_path_validate_str_length(repo, p) < 0)
182 0           return -1;
183              
184 3715           filename = p->ptr;
185             } else {
186 701           filename = source->filename;
187             }
188              
189 4416 50         if (wd && !git__prefixcmp(filename, wd))
    100          
190 3703           filename += strlen(wd);
191              
192             /* check cache for existing entry */
193 4416 50         if ((error = attr_cache_lock(cache)) < 0)
194 0           goto cleanup;
195              
196 4416           entry = attr_cache_lookup_entry(cache, filename);
197              
198 4416 100         if (!entry) {
199 101           error = attr_cache_make_entry(&entry, repo, filename);
200 4315 100         } else if (entry->file[source->type] != NULL) {
201 3043           file = entry->file[source->type];
202 3043           GIT_REFCOUNT_INC(file);
203             }
204              
205 4416           attr_cache_unlock(cache);
206              
207             cleanup:
208 4416           *out_file = file;
209 4416           *out_entry = entry;
210              
211 4416           git_str_dispose(&path);
212 4416           return error;
213             }
214              
215 4416           int git_attr_cache__get(
216             git_attr_file **out,
217             git_repository *repo,
218             git_attr_session *attr_session,
219             git_attr_file_source *source,
220             git_attr_file_parser parser,
221             bool allow_macros)
222             {
223 4416           int error = 0;
224 4416           git_attr_cache *cache = git_repository_attr_cache(repo);
225 4416           git_attr_file_entry *entry = NULL;
226 4416           git_attr_file *file = NULL, *updated = NULL;
227              
228 4416 50         if ((error = attr_cache_lookup(&file, &entry, repo, attr_session, source)) < 0)
229 0           return error;
230              
231             /* load file if we don't have one or if existing one is out of date */
232 4416 100         if (!file ||
    100          
233 3043           (error = git_attr_file__out_of_date(repo, attr_session, file, source)) > 0)
234 3698           error = git_attr_file__load(&updated, repo, attr_session,
235             entry, source, parser,
236             allow_macros);
237              
238             /* if we loaded the file, insert into and/or update cache */
239 4416 100         if (updated) {
240 2426 50         if ((error = attr_cache_upsert(cache, updated)) < 0) {
241 0           git_attr_file__free(updated);
242             } else {
243 2426           git_attr_file__free(file); /* offset incref from lookup */
244 2426           file = updated;
245             }
246             }
247              
248             /* if file could not be loaded */
249 4416 100         if (error < 0) {
250             /* remove existing entry */
251 1272 50         if (file) {
252 0           attr_cache_remove(cache, file);
253 0           git_attr_file__free(file); /* offset incref from lookup */
254 0           file = NULL;
255             }
256             /* no error if file simply doesn't exist */
257 1272 50         if (error == GIT_ENOTFOUND) {
258 1272           git_error_clear();
259 1272           error = 0;
260             }
261             }
262              
263 4416           *out = file;
264 4416           return error;
265             }
266              
267 0           bool git_attr_cache__is_cached(
268             git_repository *repo,
269             git_attr_file_source_t source_type,
270             const char *filename)
271             {
272 0           git_attr_cache *cache = git_repository_attr_cache(repo);
273             git_attr_file_entry *entry;
274             git_strmap *files;
275              
276 0 0         if (!cache || !(files = cache->files))
    0          
277 0           return false;
278              
279 0 0         if ((entry = git_strmap_get(files, filename)) == NULL)
280 0           return false;
281              
282 0 0         return entry && (entry->file[source_type] != NULL);
    0          
283             }
284              
285              
286 32           static int attr_cache__lookup_path(
287             char **out, git_config *cfg, const char *key, const char *fallback)
288             {
289 32           git_str buf = GIT_STR_INIT;
290             int error;
291 32           git_config_entry *entry = NULL;
292              
293 32           *out = NULL;
294              
295 32 50         if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
296 0           return error;
297              
298 32 50         if (entry) {
299 0           const char *cfgval = entry->value;
300              
301             /* expand leading ~/ as needed */
302 0 0         if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') {
    0          
    0          
303 0 0         if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2])))
304 0           *out = git_str_detach(&buf);
305 0 0         } else if (cfgval) {
306 0           *out = git__strdup(cfgval);
307             }
308             }
309 32 50         else if (!git_sysdir_find_xdg_file(&buf, fallback)) {
310 0           *out = git_str_detach(&buf);
311             }
312              
313 32           git_config_entry_free(entry);
314 32           git_str_dispose(&buf);
315              
316 32           return error;
317             }
318              
319 16           static void attr_cache__free(git_attr_cache *cache)
320             {
321             bool unlock;
322              
323 16 50         if (!cache)
324 0           return;
325              
326 16           unlock = (attr_cache_lock(cache) == 0);
327              
328 16 50         if (cache->files != NULL) {
329             git_attr_file_entry *entry;
330             git_attr_file *file;
331             int i;
332              
333 622 100         git_strmap_foreach_value(cache->files, entry, {
    100          
    100          
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);
338             }
339             }
340             });
341 16           git_strmap_free(cache->files);
342             }
343              
344 16 50         if (cache->macros != NULL) {
345             git_attr_rule *rule;
346              
347 32 100         git_strmap_foreach_value(cache->macros, rule, {
348             git_attr_rule__free(rule);
349             });
350 16           git_strmap_free(cache->macros);
351             }
352              
353 16           git_pool_clear(&cache->pool);
354              
355 16           git__free(cache->cfg_attr_file);
356 16           cache->cfg_attr_file = NULL;
357              
358 16           git__free(cache->cfg_excl_file);
359 16           cache->cfg_excl_file = NULL;
360              
361 16 50         if (unlock)
362 16           attr_cache_unlock(cache);
363 16           git_mutex_free(&cache->lock);
364              
365 16           git__free(cache);
366             }
367              
368 899           int git_attr_cache__init(git_repository *repo)
369             {
370 899           int ret = 0;
371 899           git_attr_cache *cache = git_repository_attr_cache(repo);
372 899           git_config *cfg = NULL;
373              
374 899 100         if (cache)
375 883           return 0;
376              
377 16           cache = git__calloc(1, sizeof(git_attr_cache));
378 16 50         GIT_ERROR_CHECK_ALLOC(cache);
379              
380             /* set up lock */
381 16 50         if (git_mutex_init(&cache->lock) < 0) {
382 0           git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache");
383 0           git__free(cache);
384 0           return -1;
385             }
386              
387 16 50         if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
388 0           goto cancel;
389              
390             /* cache config settings for attributes and ignores */
391 16           ret = attr_cache__lookup_path(
392             &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
393 16 50         if (ret < 0)
394 0           goto cancel;
395              
396 16           ret = attr_cache__lookup_path(
397             &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
398 16 50         if (ret < 0)
399 0           goto cancel;
400              
401             /* allocate hashtable for attribute and ignore file contents,
402             * hashtable for attribute macros, and string pool
403             */
404 16 50         if ((ret = git_strmap_new(&cache->files)) < 0 ||
    50          
405 16 50         (ret = git_strmap_new(&cache->macros)) < 0 ||
406 16           (ret = git_pool_init(&cache->pool, 1)) < 0)
407             goto cancel;
408              
409 16 50         if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL)
410 0           goto cancel; /* raced with another thread, free this but no error */
411              
412 16           git_config_free(cfg);
413              
414             /* insert default macros */
415 16           return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf");
416              
417             cancel:
418 0           attr_cache__free(cache);
419 0           git_config_free(cfg);
420 899           return ret;
421             }
422              
423 63           int git_attr_cache_flush(git_repository *repo)
424             {
425             git_attr_cache *cache;
426              
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...
429             */
430 63 50         if (repo && (cache = git_atomic_swap(repo->attrcache, NULL)) != NULL)
    100          
431 16           attr_cache__free(cache);
432              
433 63           return 0;
434             }
435              
436 16           int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
437             {
438 16           git_attr_cache *cache = git_repository_attr_cache(repo);
439             git_attr_rule *preexisting;
440 16           bool locked = false;
441 16           int error = 0;
442              
443             /*
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.
448             *
449             * TODO: generate warning log if (macro->assigns.length == 0)
450             */
451 16 50         if (macro->assigns.length == 0) {
452 0           git_attr_rule__free(macro);
453 0           goto out;
454             }
455              
456 16 50         if ((error = attr_cache_lock(cache)) < 0)
457 0           goto out;
458 16           locked = true;
459              
460 16 50         if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL)
461 0           git_attr_rule__free(preexisting);
462              
463 16 50         if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0)
464 0           goto out;
465              
466             out:
467 16 50         if (locked)
468 16           attr_cache_unlock(cache);
469 16           return error;
470             }
471              
472 0           git_attr_rule *git_attr_cache__lookup_macro(
473             git_repository *repo, const char *name)
474             {
475 0           git_strmap *macros = git_repository_attr_cache(repo)->macros;
476              
477 0           return git_strmap_get(macros, name);
478             }