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