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