File Coverage

deps/libgit2/src/attrcache.c
Criterion Covered Total %
statement 151 212 71.2
branch 74 140 52.8
condition n/a
subroutine n/a
pod n/a
total 225 352 63.9


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