]> git.proxmox.com Git - libgit2.git/blob - src/attr.c
Merge pull request #570 from arrbee/uniform-iterators
[libgit2.git] / src / attr.c
1 #include "repository.h"
2 #include "fileops.h"
3 #include "config.h"
4 #include <ctype.h>
5
6 static int collect_attr_files(
7 git_repository *repo, const char *path, git_vector *files);
8
9
10 int git_attr_get(
11 git_repository *repo, const char *pathname,
12 const char *name, const char **value)
13 {
14 int error;
15 git_attr_path path;
16 git_vector files = GIT_VECTOR_INIT;
17 unsigned int i, j;
18 git_attr_file *file;
19 git_attr_name attr;
20 git_attr_rule *rule;
21
22 *value = NULL;
23
24 if ((error = git_attr_path__init(
25 &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS ||
26 (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
27 return git__rethrow(error, "Could not get attribute for %s", pathname);
28
29 attr.name = name;
30 attr.name_hash = git_attr_file__name_hash(name);
31
32 git_vector_foreach(&files, i, file) {
33
34 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
35 int pos = git_vector_bsearch(&rule->assigns, &attr);
36 git_clearerror(); /* okay if search failed */
37
38 if (pos >= 0) {
39 *value = ((git_attr_assignment *)git_vector_get(
40 &rule->assigns, pos))->value;
41 goto found;
42 }
43 }
44 }
45
46 found:
47 git_vector_free(&files);
48
49 return error;
50 }
51
52
53 typedef struct {
54 git_attr_name name;
55 git_attr_assignment *found;
56 } attr_get_many_info;
57
58 int git_attr_get_many(
59 git_repository *repo, const char *pathname,
60 size_t num_attr, const char **names, const char **values)
61 {
62 int error;
63 git_attr_path path;
64 git_vector files = GIT_VECTOR_INIT;
65 unsigned int i, j, k;
66 git_attr_file *file;
67 git_attr_rule *rule;
68 attr_get_many_info *info = NULL;
69 size_t num_found = 0;
70
71 memset((void *)values, 0, sizeof(const char *) * num_attr);
72
73 if ((error = git_attr_path__init(
74 &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS ||
75 (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
76 return git__rethrow(error, "Could not get attributes for %s", pathname);
77
78 if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) {
79 git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname);
80 goto cleanup;
81 }
82
83 git_vector_foreach(&files, i, file) {
84
85 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
86
87 for (k = 0; k < num_attr; k++) {
88 int pos;
89
90 if (info[k].found != NULL) /* already found assignment */
91 continue;
92
93 if (!info[k].name.name) {
94 info[k].name.name = names[k];
95 info[k].name.name_hash = git_attr_file__name_hash(names[k]);
96 }
97
98 pos = git_vector_bsearch(&rule->assigns, &info[k].name);
99 git_clearerror(); /* okay if search failed */
100
101 if (pos >= 0) {
102 info[k].found = (git_attr_assignment *)
103 git_vector_get(&rule->assigns, pos);
104 values[k] = info[k].found->value;
105
106 if (++num_found == num_attr)
107 goto cleanup;
108 }
109 }
110 }
111 }
112
113 cleanup:
114 git_vector_free(&files);
115 git__free(info);
116
117 return error;
118 }
119
120
121 int git_attr_foreach(
122 git_repository *repo, const char *pathname,
123 int (*callback)(const char *name, const char *value, void *payload),
124 void *payload)
125 {
126 int error;
127 git_attr_path path;
128 git_vector files = GIT_VECTOR_INIT;
129 unsigned int i, j, k;
130 git_attr_file *file;
131 git_attr_rule *rule;
132 git_attr_assignment *assign;
133 git_hashtable *seen = NULL;
134
135 if ((error = git_attr_path__init(
136 &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS ||
137 (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
138 return git__rethrow(error, "Could not get attributes for %s", pathname);
139
140 seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb);
141 if (!seen) {
142 error = GIT_ENOMEM;
143 goto cleanup;
144 }
145
146 git_vector_foreach(&files, i, file) {
147
148 git_attr_file__foreach_matching_rule(file, &path, j, rule) {
149
150 git_vector_foreach(&rule->assigns, k, assign) {
151 /* skip if higher priority assignment was already seen */
152 if (git_hashtable_lookup(seen, assign->name))
153 continue;
154
155 error = git_hashtable_insert(seen, assign->name, assign);
156 if (error != GIT_SUCCESS)
157 goto cleanup;
158
159 error = callback(assign->name, assign->value, payload);
160 if (error != GIT_SUCCESS)
161 goto cleanup;
162 }
163 }
164 }
165
166 cleanup:
167 if (seen)
168 git_hashtable_free(seen);
169 git_vector_free(&files);
170
171 if (error != GIT_SUCCESS)
172 (void)git__rethrow(error, "Could not get attributes for %s", pathname);
173
174 return error;
175 }
176
177
178 int git_attr_add_macro(
179 git_repository *repo,
180 const char *name,
181 const char *values)
182 {
183 int error;
184 git_attr_rule *macro = NULL;
185
186 if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS)
187 return error;
188
189 macro = git__calloc(1, sizeof(git_attr_rule));
190 if (!macro)
191 return GIT_ENOMEM;
192
193 macro->match.pattern = git__strdup(name);
194 if (!macro->match.pattern) {
195 git__free(macro);
196 return GIT_ENOMEM;
197 }
198
199 macro->match.length = strlen(macro->match.pattern);
200 macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
201
202 error = git_attr_assignment__parse(repo, &macro->assigns, &values);
203
204 if (error == GIT_SUCCESS)
205 error = git_attr_cache__insert_macro(repo, macro);
206
207 if (error < GIT_SUCCESS)
208 git_attr_rule__free(macro);
209
210 return error;
211 }
212
213 int git_attr_cache__is_cached(git_repository *repo, const char *path)
214 {
215 const char *cache_key = path;
216 if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
217 cache_key += strlen(git_repository_workdir(repo));
218 return (git_hashtable_lookup(repo->attrcache.files, cache_key) == NULL);
219 }
220
221 int git_attr_cache__lookup_or_create_file(
222 git_repository *repo,
223 const char *key,
224 const char *filename,
225 int (*loader)(git_repository *, const char *, git_attr_file *),
226 git_attr_file **file_ptr)
227 {
228 int error;
229 git_attr_cache *cache = &repo->attrcache;
230 git_attr_file *file = NULL;
231
232 file = git_hashtable_lookup(cache->files, key);
233 if (file) {
234 *file_ptr = file;
235 return GIT_SUCCESS;
236 }
237
238 if (loader && git_path_exists(filename) != GIT_SUCCESS) {
239 *file_ptr = NULL;
240 return GIT_SUCCESS;
241 }
242
243 if ((error = git_attr_file__new(&file)) < GIT_SUCCESS)
244 return error;
245
246 if (loader)
247 error = loader(repo, filename, file);
248 else
249 error = git_attr_file__set_path(repo, key, file);
250
251 if (error == GIT_SUCCESS)
252 error = git_hashtable_insert(cache->files, file->path, file);
253
254 if (error < GIT_SUCCESS) {
255 git_attr_file__free(file);
256 file = NULL;
257 }
258
259 *file_ptr = file;
260 return error;
261 }
262
263 /* add git_attr_file to vector of files, loading if needed */
264 int git_attr_cache__push_file(
265 git_repository *repo,
266 git_vector *stack,
267 const char *base,
268 const char *filename,
269 int (*loader)(git_repository *, const char *, git_attr_file *))
270 {
271 int error;
272 git_buf path = GIT_BUF_INIT;
273 git_attr_file *file = NULL;
274 const char *cache_key;
275
276 if (base != NULL) {
277 if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS)
278 return error;
279 filename = path.ptr;
280 }
281
282 /* either get attr_file from cache or read from disk */
283 cache_key = filename;
284 if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
285 cache_key += strlen(git_repository_workdir(repo));
286
287 error = git_attr_cache__lookup_or_create_file(
288 repo, cache_key, filename, loader, &file);
289
290 if (error == GIT_SUCCESS && file != NULL)
291 error = git_vector_insert(stack, file);
292
293 git_buf_free(&path);
294 return error;
295 }
296
297 #define push_attrs(R,S,B,F) \
298 git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file)
299
300 typedef struct {
301 git_repository *repo;
302 git_vector *files;
303 } attr_walk_up_info;
304
305 static int push_one_attr(void *ref, git_buf *path)
306 {
307 attr_walk_up_info *info = (attr_walk_up_info *)ref;
308 return push_attrs(info->repo, info->files, path->ptr, GIT_ATTR_FILE);
309 }
310
311 static int collect_attr_files(
312 git_repository *repo, const char *path, git_vector *files)
313 {
314 int error = GIT_SUCCESS;
315 git_buf dir = GIT_BUF_INIT;
316 git_config *cfg;
317 const char *workdir = git_repository_workdir(repo);
318 attr_walk_up_info info;
319
320 if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS)
321 goto cleanup;
322
323 if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS)
324 goto cleanup;
325
326 if ((error = git_path_find_dir(&dir, path, workdir)) < GIT_SUCCESS)
327 goto cleanup;
328
329 /* in precendence order highest to lowest:
330 * - $GIT_DIR/info/attributes
331 * - path components with .gitattributes
332 * - config core.attributesfile
333 * - $GIT_PREFIX/etc/gitattributes
334 */
335
336 error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO);
337 if (error < GIT_SUCCESS)
338 goto cleanup;
339
340 info.repo = repo;
341 info.files = files;
342 error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
343 if (error < GIT_SUCCESS)
344 goto cleanup;
345
346 if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) {
347 const char *core_attribs = NULL;
348 git_config_get_string(cfg, GIT_ATTR_CONFIG, &core_attribs);
349 git_clearerror(); /* don't care if attributesfile is not set */
350 if (core_attribs)
351 error = push_attrs(repo, files, NULL, core_attribs);
352 git_config_free(cfg);
353 }
354
355 if (error == GIT_SUCCESS) {
356 error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
357 if (error == GIT_SUCCESS)
358 error = push_attrs(repo, files, NULL, dir.ptr);
359 else if (error == GIT_ENOTFOUND)
360 error = GIT_SUCCESS;
361 }
362
363 cleanup:
364 if (error < GIT_SUCCESS) {
365 git__rethrow(error, "Could not get attributes for '%s'", path);
366 git_vector_free(files);
367 }
368 git_buf_free(&dir);
369
370 return error;
371 }
372
373
374 int git_attr_cache__init(git_repository *repo)
375 {
376 int error = GIT_SUCCESS;
377 git_attr_cache *cache = &repo->attrcache;
378
379 if (cache->initialized)
380 return GIT_SUCCESS;
381
382 if (cache->files == NULL) {
383 cache->files = git_hashtable_alloc(
384 8, git_hash__strhash_cb, git_hash__strcmp_cb);
385 if (!cache->files)
386 return git__throw(GIT_ENOMEM, "Could not initialize attribute cache");
387 }
388
389 if (cache->macros == NULL) {
390 cache->macros = git_hashtable_alloc(
391 8, git_hash__strhash_cb, git_hash__strcmp_cb);
392 if (!cache->macros)
393 return git__throw(GIT_ENOMEM, "Could not initialize attribute cache");
394 }
395
396 cache->initialized = 1;
397
398 /* insert default macros */
399 error = git_attr_add_macro(repo, "binary", "-diff -crlf");
400
401 return error;
402 }
403
404 void git_attr_cache_flush(
405 git_repository *repo)
406 {
407 if (!repo)
408 return;
409
410 if (repo->attrcache.files) {
411 const void *GIT_UNUSED(name);
412 git_attr_file *file;
413
414 GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file,
415 git_attr_file__free(file));
416
417 git_hashtable_free(repo->attrcache.files);
418 repo->attrcache.files = NULL;
419 }
420
421 if (repo->attrcache.macros) {
422 const void *GIT_UNUSED(name);
423 git_attr_rule *rule;
424
425 GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule,
426 git_attr_rule__free(rule));
427
428 git_hashtable_free(repo->attrcache.macros);
429 repo->attrcache.macros = NULL;
430 }
431
432 repo->attrcache.initialized = 0;
433 }
434
435 int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
436 {
437 if (macro->assigns.length == 0)
438 return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values");
439
440 return git_hashtable_insert(
441 repo->attrcache.macros, macro->match.pattern, macro);
442 }