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