File Coverage

deps/libgit2/src/attrcache.c
Criterion Covered Total %
statement 151 212 71.2
branch 75 142 52.8
condition n/a
subroutine n/a
pod n/a
total 226 354 63.8


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 7070           GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
17             {
18             GIT_UNUSED(cache); /* avoid warning if threading is off */
19              
20 7070 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 7070           return 0;
25             }
26              
27 7070           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 7070           }
32              
33 7038           GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
34             git_attr_cache *cache, const char *path)
35             {
36 7038           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 2502           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 2502 50         if (attr_cache_lock(cache) < 0)
100 0           return -1;
101              
102 2502           entry = attr_cache_lookup_entry(cache, file->entry->path);
103              
104 2502           GIT_REFCOUNT_OWN(file, entry);
105 2502           GIT_REFCOUNT_INC(file);
106              
107             /*
108             * Replace the existing value if another thread has
109             * created it in the meantime.
110             */
111 2502           old = git__swap(entry->file[file->source], file);
112              
113 2502 100         if (old) {
114 2401           GIT_REFCOUNT_OWN(old, NULL);
115 2401           git_attr_file__free(old);
116             }
117              
118 2502           attr_cache_unlock(cache);
119 2502           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 4536           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 4536           int error = 0;
162 4536           git_buf path = GIT_BUF_INIT;
163 4536           const char *wd = git_repository_workdir(repo), *relfile;
164 4536           git_attr_cache *cache = git_repository_attr_cache(repo);
165 4536           git_attr_file_entry *entry = NULL;
166 4536           git_attr_file *file = NULL;
167              
168             /* join base and path as needed */
169 4536 100         if (base != NULL && git_path_root(filename) < 0) {
    50          
170 3813 100         git_buf *p = attr_session ? &attr_session->tmp : &path;
171              
172 3813 50         if (git_buf_joinpath(p, base, filename) < 0)
173 0           return -1;
174              
175 3813           filename = p->ptr;
176             }
177              
178 4536           relfile = filename;
179 4536 50         if (wd && !git__prefixcmp(relfile, wd))
    100          
180 3801           relfile += strlen(wd);
181              
182             /* check cache for existing entry */
183 4536 50         if ((error = attr_cache_lock(cache)) < 0)
184 0           goto cleanup;
185              
186 4536           entry = attr_cache_lookup_entry(cache, relfile);
187 4536 100         if (!entry)
188 101           error = attr_cache_make_entry(&entry, repo, relfile);
189 4435 100         else if (entry->file[source] != NULL) {
190 3127           file = entry->file[source];
191 3127           GIT_REFCOUNT_INC(file);
192             }
193              
194 4536           attr_cache_unlock(cache);
195              
196             cleanup:
197 4536           *out_file = file;
198 4536           *out_entry = entry;
199              
200 4536           git_buf_dispose(&path);
201 4536           return error;
202             }
203              
204 4536           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 4536           int error = 0;
215 4536           git_attr_cache *cache = git_repository_attr_cache(repo);
216 4536           git_attr_file_entry *entry = NULL;
217 4536           git_attr_file *file = NULL, *updated = NULL;
218              
219 4536 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 4536 100         if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
    100          
225 3810           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 4536 100         if (updated) {
229 2502 50         if ((error = attr_cache_upsert(cache, updated)) < 0)
230 0           git_attr_file__free(updated);
231             else {
232 2502           git_attr_file__free(file); /* offset incref from lookup */
233 2502           file = updated;
234             }
235             }
236              
237             /* if file could not be loaded */
238 4536 100         if (error < 0) {
239             /* remove existing entry */
240 1308 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 1308 50         if (error == GIT_ENOTFOUND) {
247 1308           git_error_clear();
248 1308           error = 0;
249             }
250             }
251              
252 4536           *out = file;
253 4536           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 925           int git_attr_cache__init(git_repository *repo)
358             {
359 925           int ret = 0;
360 925           git_attr_cache *cache = git_repository_attr_cache(repo);
361 925           git_config *cfg = NULL;
362              
363 925 100         if (cache)
364 909           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 50         (ret = git_strmap_new(&cache->macros)) < 0 ||
395 16           (ret = git_pool_init(&cache->pool, 1)) < 0)
396             goto cancel;
397              
398 16           cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
399 16 50         if (cache)
400 0           goto cancel; /* raced with another thread, free this but no error */
401              
402 16           git_config_free(cfg);
403              
404             /* insert default macros */
405 16           return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf");
406              
407             cancel:
408 0           attr_cache__free(cache);
409 0           git_config_free(cfg);
410 925           return ret;
411             }
412              
413 61           int git_attr_cache_flush(git_repository *repo)
414             {
415             git_attr_cache *cache;
416              
417             /* this could be done less expensively, but for now, we'll just free
418             * the entire attrcache and let the next use reinitialize it...
419             */
420 61 50         if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
    100          
421 16           attr_cache__free(cache);
422              
423 61           return 0;
424             }
425              
426 16           int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
427             {
428 16           git_attr_cache *cache = git_repository_attr_cache(repo);
429             git_attr_rule *preexisting;
430 16           bool locked = false;
431 16           int error = 0;
432              
433             /*
434             * Callers assume that if we return success, that the
435             * macro will have been adopted by the attributes cache.
436             * Thus, we have to free the macro here if it's not being
437             * added to the cache.
438             *
439             * TODO: generate warning log if (macro->assigns.length == 0)
440             */
441 16 50         if (macro->assigns.length == 0) {
442 0           git_attr_rule__free(macro);
443 0           goto out;
444             }
445              
446 16 50         if ((error = attr_cache_lock(cache)) < 0)
447 0           goto out;
448 16           locked = true;
449              
450 16 50         if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL)
451 0           git_attr_rule__free(preexisting);
452              
453 16 50         if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0)
454 0           goto out;
455              
456             out:
457 16 50         if (locked)
458 16           attr_cache_unlock(cache);
459 16           return error;
460             }
461              
462 0           git_attr_rule *git_attr_cache__lookup_macro(
463             git_repository *repo, const char *name)
464             {
465 0           git_strmap *macros = git_repository_attr_cache(repo)->macros;
466              
467 0           return git_strmap_get(macros, name);
468             }