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