]> git.proxmox.com Git - libgit2.git/blob - src/attrcache.c
Merge pull request #2883 from urkud/reget-reader-pointer
[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, (uint32_t)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_session *attr_session,
153 git_attr_file_source source,
154 const char *base,
155 const char *filename)
156 {
157 int error = 0;
158 git_buf path = GIT_BUF_INIT;
159 const char *wd = git_repository_workdir(repo), *relfile;
160 git_attr_cache *cache = git_repository_attr_cache(repo);
161 git_attr_file_entry *entry = NULL;
162 git_attr_file *file = NULL;
163
164 /* join base and path as needed */
165 if (base != NULL && git_path_root(filename) < 0) {
166 git_buf *p = attr_session ? &attr_session->tmp : &path;
167
168 if (git_buf_joinpath(p, base, filename) < 0)
169 return -1;
170
171 filename = p->ptr;
172 }
173
174 relfile = filename;
175 if (wd && !git__prefixcmp(relfile, wd))
176 relfile += strlen(wd);
177
178 /* check cache for existing entry */
179 if ((error = attr_cache_lock(cache)) < 0)
180 goto cleanup;
181
182 entry = attr_cache_lookup_entry(cache, relfile);
183 if (!entry)
184 error = attr_cache_make_entry(&entry, repo, relfile);
185 else if (entry->file[source] != NULL) {
186 file = entry->file[source];
187 GIT_REFCOUNT_INC(file);
188 }
189
190 attr_cache_unlock(cache);
191
192 cleanup:
193 *out_file = file;
194 *out_entry = entry;
195
196 git_buf_free(&path);
197 return error;
198 }
199
200 int git_attr_cache__get(
201 git_attr_file **out,
202 git_repository *repo,
203 git_attr_session *attr_session,
204 git_attr_file_source source,
205 const char *base,
206 const char *filename,
207 git_attr_file_parser parser)
208 {
209 int error = 0;
210 git_attr_cache *cache = git_repository_attr_cache(repo);
211 git_attr_file_entry *entry = NULL;
212 git_attr_file *file = NULL, *updated = NULL;
213
214 if ((error = attr_cache_lookup(
215 &file, &entry, repo, attr_session, source, base, filename)) < 0)
216 return error;
217
218 /* load file if we don't have one or if existing one is out of date */
219 if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
220 error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser);
221
222 /* if we loaded the file, insert into and/or update cache */
223 if (updated) {
224 if ((error = attr_cache_upsert(cache, updated)) < 0)
225 git_attr_file__free(updated);
226 else {
227 git_attr_file__free(file); /* offset incref from lookup */
228 file = updated;
229 }
230 }
231
232 /* if file could not be loaded */
233 if (error < 0) {
234 /* remove existing entry */
235 if (file) {
236 attr_cache_remove(cache, file);
237 git_attr_file__free(file); /* offset incref from lookup */
238 file = NULL;
239 }
240 /* no error if file simply doesn't exist */
241 if (error == GIT_ENOTFOUND) {
242 giterr_clear();
243 error = 0;
244 }
245 }
246
247 *out = file;
248 return error;
249 }
250
251 bool git_attr_cache__is_cached(
252 git_repository *repo,
253 git_attr_file_source source,
254 const char *filename)
255 {
256 git_attr_cache *cache = git_repository_attr_cache(repo);
257 git_strmap *files;
258 khiter_t pos;
259 git_attr_file_entry *entry;
260
261 if (!cache || !(files = cache->files))
262 return false;
263
264 pos = git_strmap_lookup_index(files, filename);
265 if (!git_strmap_valid_index(files, pos))
266 return false;
267
268 entry = git_strmap_value_at(files, pos);
269
270 return entry && (entry->file[source] != NULL);
271 }
272
273
274 static int attr_cache__lookup_path(
275 char **out, git_config *cfg, const char *key, const char *fallback)
276 {
277 git_buf buf = GIT_BUF_INIT;
278 int error;
279 const git_config_entry *entry = NULL;
280
281 *out = NULL;
282
283 if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
284 return error;
285
286 if (entry) {
287 const char *cfgval = entry->value;
288
289 /* expand leading ~/ as needed */
290 if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
291 !git_sysdir_find_global_file(&buf, &cfgval[2]))
292 *out = git_buf_detach(&buf);
293 else if (cfgval)
294 *out = git__strdup(cfgval);
295 }
296 else if (!git_sysdir_find_xdg_file(&buf, fallback))
297 *out = git_buf_detach(&buf);
298
299 git_buf_free(&buf);
300
301 return error;
302 }
303
304 static void attr_cache__free(git_attr_cache *cache)
305 {
306 bool unlock;
307
308 if (!cache)
309 return;
310
311 unlock = (git_mutex_lock(&cache->lock) == 0);
312
313 if (cache->files != NULL) {
314 git_attr_file_entry *entry;
315 git_attr_file *file;
316 int i;
317
318 git_strmap_foreach_value(cache->files, entry, {
319 for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
320 if ((file = git__swap(entry->file[i], NULL)) != NULL) {
321 GIT_REFCOUNT_OWN(file, NULL);
322 git_attr_file__free(file);
323 }
324 }
325 });
326 git_strmap_free(cache->files);
327 }
328
329 if (cache->macros != NULL) {
330 git_attr_rule *rule;
331
332 git_strmap_foreach_value(cache->macros, rule, {
333 git_attr_rule__free(rule);
334 });
335 git_strmap_free(cache->macros);
336 }
337
338 git_pool_clear(&cache->pool);
339
340 git__free(cache->cfg_attr_file);
341 cache->cfg_attr_file = NULL;
342
343 git__free(cache->cfg_excl_file);
344 cache->cfg_excl_file = NULL;
345
346 if (unlock)
347 git_mutex_unlock(&cache->lock);
348 git_mutex_free(&cache->lock);
349
350 git__free(cache);
351 }
352
353 int git_attr_cache__do_init(git_repository *repo)
354 {
355 int ret = 0;
356 git_attr_cache *cache = git_repository_attr_cache(repo);
357 git_config *cfg = NULL;
358
359 if (cache)
360 return 0;
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 if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
373 goto cancel;
374
375 /* cache config settings for attributes and ignores */
376 ret = attr_cache__lookup_path(
377 &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
378 if (ret < 0)
379 goto cancel;
380
381 ret = attr_cache__lookup_path(
382 &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
383 if (ret < 0)
384 goto cancel;
385
386 /* allocate hashtable for attribute and ignore file contents,
387 * hashtable for attribute macros, and string pool
388 */
389 if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
390 (ret = git_strmap_alloc(&cache->macros)) < 0 ||
391 (ret = git_pool_init(&cache->pool, 1, 0)) < 0)
392 goto cancel;
393
394 cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
395 if (cache)
396 goto cancel; /* raced with another thread, free this but no error */
397
398 git_config_free(cfg);
399
400 /* insert default macros */
401 return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
402
403 cancel:
404 attr_cache__free(cache);
405 git_config_free(cfg);
406 return ret;
407 }
408
409 void git_attr_cache_flush(git_repository *repo)
410 {
411 git_attr_cache *cache;
412
413 /* this could be done less expensively, but for now, we'll just free
414 * the entire attrcache and let the next use reinitialize it...
415 */
416 if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
417 attr_cache__free(cache);
418 }
419
420 int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
421 {
422 git_attr_cache *cache = git_repository_attr_cache(repo);
423 git_strmap *macros = cache->macros;
424 int error;
425
426 /* TODO: generate warning log if (macro->assigns.length == 0) */
427 if (macro->assigns.length == 0)
428 return 0;
429
430 if (git_mutex_lock(&cache->lock) < 0) {
431 giterr_set(GITERR_OS, "Unable to get attr cache lock");
432 error = -1;
433 } else {
434 git_strmap_insert(macros, macro->match.pattern, macro, error);
435 git_mutex_unlock(&cache->lock);
436 }
437
438 return (error < 0) ? -1 : 0;
439 }
440
441 git_attr_rule *git_attr_cache__lookup_macro(
442 git_repository *repo, const char *name)
443 {
444 git_strmap *macros = git_repository_attr_cache(repo)->macros;
445 khiter_t pos;
446
447 pos = git_strmap_lookup_index(macros, name);
448
449 if (!git_strmap_valid_index(macros, pos))
450 return NULL;
451
452 return (git_attr_rule *)git_strmap_value_at(macros, pos);
453 }
454