]>
Commit | Line | Data |
---|---|---|
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 |
16 | GIT_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 | ||
27 | 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 | } | |
32 | ||
823c0e9c | 33 | GIT_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 |
39 | int 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 */ | |
79 | static 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 */ | |
98 | static 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 | ||
126 | static 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 |
156 | static 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 | 202 | cleanup: |
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 | ||
210 | int 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 | ||
262 | bool 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 | ||
281 | static 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 | ||
314 | static 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 | 363 | int 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 | |
412 | cancel: | |
413 | attr_cache__free(cache); | |
2b52a0bf | 414 | git_config_free(cfg); |
7d490872 RB |
415 | return ret; |
416 | } | |
417 | ||
22a2d3d5 | 418 | int 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 | ||
431 | int 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 | ||
461 | out: | |
462 | if (locked) | |
463 | attr_cache_unlock(cache); | |
464 | return error; | |
7d490872 RB |
465 | } |
466 | ||
467 | git_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 | } |