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