]>
Commit | Line | Data |
---|---|---|
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 | |
11 | static int collect_attr_files( | |
12 | git_repository *repo, const char *path, git_vector *files); | |
13 | ||
14 | ||
15 | int 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 | ||
50 | found: | |
51 | git_vector_free(&files); | |
52 | ||
53 | return error; | |
54 | } | |
55 | ||
56 | ||
57 | typedef struct { | |
58 | git_attr_name name; | |
59 | git_attr_assignment *found; | |
60 | } attr_get_many_info; | |
61 | ||
62 | int 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 | ||
116 | cleanup: | |
117 | git_vector_free(&files); | |
118 | git__free(info); | |
119 | ||
120 | return error; | |
121 | } | |
122 | ||
123 | ||
124 | int 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 | ||
168 | cleanup: | |
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 |
180 | int 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, ¯o->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 | 217 | int 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 | ||
251 | cleanup: | |
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 | |
259 | static 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 | 332 | int 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 |
362 | void 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 | |
393 | int 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 | } |