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