]>
Commit | Line | Data |
---|---|---|
157cef10 | 1 | #include "common.h" |
73b51450 | 2 | #include "repository.h" |
83634d38 | 3 | #include "sysdir.h" |
ee1f0b1a | 4 | #include "config.h" |
7d490872 | 5 | #include "attr_file.h" |
5540d947 | 6 | #include "ignore.h" |
c07d9c95 | 7 | #include "git2/oid.h" |
ee1f0b1a RB |
8 | #include <ctype.h> |
9 | ||
c2b67043 | 10 | GIT__USE_STRMAP; |
01fed0a8 | 11 | |
0c9eacf3 VM |
12 | const char *git_attr__true = "[internal]__TRUE__"; |
13 | const char *git_attr__false = "[internal]__FALSE__"; | |
14 | const char *git_attr__unset = "[internal]__UNSET__"; | |
15 | ||
16 | git_attr_t git_attr_value(const char *attr) | |
17 | { | |
18 | if (attr == NULL || attr == git_attr__unset) | |
19 | return GIT_ATTR_UNSPECIFIED_T; | |
20 | ||
21 | if (attr == git_attr__true) | |
22 | return GIT_ATTR_TRUE_T; | |
23 | ||
24 | if (attr == git_attr__false) | |
25 | return GIT_ATTR_FALSE_T; | |
26 | ||
27 | return GIT_ATTR_VALUE_T; | |
28 | } | |
29 | ||
ee1f0b1a | 30 | static int collect_attr_files( |
f917481e | 31 | git_repository *repo, |
9f779aac | 32 | git_attr_session *attr_session, |
f917481e RB |
33 | uint32_t flags, |
34 | const char *path, | |
35 | git_vector *files); | |
ee1f0b1a | 36 | |
40ed4990 | 37 | static void release_attr_files(git_vector *files); |
ee1f0b1a RB |
38 | |
39 | int git_attr_get( | |
29e948de | 40 | const char **value, |
0cb16fe9 | 41 | git_repository *repo, |
f917481e RB |
42 | uint32_t flags, |
43 | const char *pathname, | |
29e948de | 44 | const char *name) |
ee1f0b1a RB |
45 | { |
46 | int error; | |
47 | git_attr_path path; | |
48 | git_vector files = GIT_VECTOR_INIT; | |
b8457baa | 49 | size_t i, j; |
ee1f0b1a RB |
50 | git_attr_file *file; |
51 | git_attr_name attr; | |
52 | git_attr_rule *rule; | |
53 | ||
3cf11eef RB |
54 | assert(value && repo && name); |
55 | ||
ee1f0b1a RB |
56 | *value = NULL; |
57 | ||
d58336dd RB |
58 | if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) |
59 | return -1; | |
60 | ||
9f779aac | 61 | if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0) |
d58336dd | 62 | goto cleanup; |
ee1f0b1a | 63 | |
2e9d813b | 64 | memset(&attr, 0, sizeof(attr)); |
ee1f0b1a RB |
65 | attr.name = name; |
66 | attr.name_hash = git_attr_file__name_hash(name); | |
67 | ||
68 | git_vector_foreach(&files, i, file) { | |
69 | ||
70 | git_attr_file__foreach_matching_rule(file, &path, j, rule) { | |
11d9f6b3 PK |
71 | size_t pos; |
72 | ||
73 | if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { | |
ee1f0b1a RB |
74 | *value = ((git_attr_assignment *)git_vector_get( |
75 | &rule->assigns, pos))->value; | |
d58336dd | 76 | goto cleanup; |
ee1f0b1a RB |
77 | } |
78 | } | |
79 | } | |
80 | ||
d58336dd | 81 | cleanup: |
40ed4990 | 82 | release_attr_files(&files); |
d58336dd | 83 | git_attr_path__free(&path); |
ee1f0b1a RB |
84 | |
85 | return error; | |
86 | } | |
87 | ||
88 | ||
89 | typedef struct { | |
90 | git_attr_name name; | |
91 | git_attr_assignment *found; | |
92 | } attr_get_many_info; | |
93 | ||
9f779aac | 94 | int git_attr_get_many_with_session( |
29e948de | 95 | const char **values, |
0cb16fe9 | 96 | git_repository *repo, |
9f779aac | 97 | git_attr_session *attr_session, |
f917481e RB |
98 | uint32_t flags, |
99 | const char *pathname, | |
0cb16fe9 | 100 | size_t num_attr, |
29e948de | 101 | const char **names) |
ee1f0b1a RB |
102 | { |
103 | int error; | |
104 | git_attr_path path; | |
105 | git_vector files = GIT_VECTOR_INIT; | |
b8457baa | 106 | size_t i, j, k; |
ee1f0b1a RB |
107 | git_attr_file *file; |
108 | git_attr_rule *rule; | |
109 | attr_get_many_info *info = NULL; | |
110 | size_t num_found = 0; | |
111 | ||
3cf11eef RB |
112 | if (!num_attr) |
113 | return 0; | |
114 | ||
115 | assert(values && repo && names); | |
116 | ||
d58336dd RB |
117 | if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) |
118 | return -1; | |
119 | ||
9f779aac | 120 | if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0) |
d58336dd | 121 | goto cleanup; |
ee1f0b1a | 122 | |
0d0fa7c3 RB |
123 | info = git__calloc(num_attr, sizeof(attr_get_many_info)); |
124 | GITERR_CHECK_ALLOC(info); | |
ee1f0b1a RB |
125 | |
126 | git_vector_foreach(&files, i, file) { | |
127 | ||
128 | git_attr_file__foreach_matching_rule(file, &path, j, rule) { | |
129 | ||
130 | for (k = 0; k < num_attr; k++) { | |
11d9f6b3 | 131 | size_t pos; |
ee1f0b1a RB |
132 | |
133 | if (info[k].found != NULL) /* already found assignment */ | |
134 | continue; | |
135 | ||
136 | if (!info[k].name.name) { | |
137 | info[k].name.name = names[k]; | |
138 | info[k].name.name_hash = git_attr_file__name_hash(names[k]); | |
139 | } | |
140 | ||
11d9f6b3 | 141 | if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { |
ee1f0b1a RB |
142 | info[k].found = (git_attr_assignment *) |
143 | git_vector_get(&rule->assigns, pos); | |
144 | values[k] = info[k].found->value; | |
145 | ||
146 | if (++num_found == num_attr) | |
147 | goto cleanup; | |
148 | } | |
149 | } | |
150 | } | |
151 | } | |
152 | ||
974774c7 RB |
153 | for (k = 0; k < num_attr; k++) { |
154 | if (!info[k].found) | |
155 | values[k] = NULL; | |
156 | } | |
157 | ||
ee1f0b1a | 158 | cleanup: |
40ed4990 | 159 | release_attr_files(&files); |
d58336dd | 160 | git_attr_path__free(&path); |
ee1f0b1a RB |
161 | git__free(info); |
162 | ||
163 | return error; | |
164 | } | |
165 | ||
9f779aac ET |
166 | int git_attr_get_many( |
167 | const char **values, | |
168 | git_repository *repo, | |
169 | uint32_t flags, | |
170 | const char *pathname, | |
171 | size_t num_attr, | |
172 | const char **names) | |
173 | { | |
174 | return git_attr_get_many_with_session( | |
175 | values, repo, NULL, flags, pathname, num_attr, names); | |
176 | } | |
ee1f0b1a RB |
177 | |
178 | int git_attr_foreach( | |
0cb16fe9 | 179 | git_repository *repo, |
f917481e RB |
180 | uint32_t flags, |
181 | const char *pathname, | |
ee1f0b1a RB |
182 | int (*callback)(const char *name, const char *value, void *payload), |
183 | void *payload) | |
184 | { | |
185 | int error; | |
186 | git_attr_path path; | |
187 | git_vector files = GIT_VECTOR_INIT; | |
b8457baa | 188 | size_t i, j, k; |
ee1f0b1a RB |
189 | git_attr_file *file; |
190 | git_attr_rule *rule; | |
191 | git_attr_assignment *assign; | |
c2b67043 | 192 | git_strmap *seen = NULL; |
ee1f0b1a | 193 | |
3cf11eef RB |
194 | assert(repo && callback); |
195 | ||
d58336dd RB |
196 | if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) |
197 | return -1; | |
198 | ||
9f779aac | 199 | if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 || |
40ed4990 | 200 | (error = git_strmap_alloc(&seen)) < 0) |
d58336dd | 201 | goto cleanup; |
ee1f0b1a | 202 | |
ee1f0b1a RB |
203 | git_vector_foreach(&files, i, file) { |
204 | ||
205 | git_attr_file__foreach_matching_rule(file, &path, j, rule) { | |
206 | ||
207 | git_vector_foreach(&rule->assigns, k, assign) { | |
208 | /* skip if higher priority assignment was already seen */ | |
c2b67043 | 209 | if (git_strmap_exists(seen, assign->name)) |
ee1f0b1a RB |
210 | continue; |
211 | ||
c2b67043 | 212 | git_strmap_insert(seen, assign->name, assign, error); |
5dca2010 RB |
213 | if (error < 0) |
214 | goto cleanup; | |
ee1f0b1a | 215 | |
c7b3e1b3 RB |
216 | error = callback(assign->name, assign->value, payload); |
217 | if (error) { | |
26c1cb91 | 218 | giterr_set_after_callback(error); |
ee1f0b1a | 219 | goto cleanup; |
c7b3e1b3 | 220 | } |
ee1f0b1a RB |
221 | } |
222 | } | |
223 | } | |
224 | ||
225 | cleanup: | |
c2b67043 | 226 | git_strmap_free(seen); |
40ed4990 | 227 | release_attr_files(&files); |
d58336dd | 228 | git_attr_path__free(&path); |
ee1f0b1a | 229 | |
ee1f0b1a RB |
230 | return error; |
231 | } | |
232 | ||
e3a2a04c RB |
233 | static int preload_attr_file( |
234 | git_repository *repo, | |
9f779aac | 235 | git_attr_session *attr_session, |
e3a2a04c RB |
236 | git_attr_file_source source, |
237 | const char *base, | |
238 | const char *file) | |
239 | { | |
240 | int error; | |
241 | git_attr_file *preload = NULL; | |
242 | ||
243 | if (!file) | |
244 | return 0; | |
245 | if (!(error = git_attr_cache__get( | |
9f779aac | 246 | &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer))) |
e3a2a04c RB |
247 | git_attr_file__free(preload); |
248 | ||
249 | return error; | |
250 | } | |
251 | ||
d4b1b767 ET |
252 | static int system_attr_file( |
253 | git_buf *out, | |
254 | git_repository *repo, | |
255 | git_attr_session *attr_session) | |
256 | { | |
257 | int error; | |
258 | ||
259 | if (!attr_session) { | |
260 | error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); | |
261 | ||
262 | if (error == GIT_ENOTFOUND) | |
263 | giterr_clear(); | |
264 | ||
265 | return error; | |
266 | } | |
267 | ||
268 | if (!attr_session->init_sysdir) { | |
269 | error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); | |
270 | ||
271 | if (error == GIT_ENOTFOUND) | |
272 | giterr_clear(); | |
273 | else if (error) | |
274 | return error; | |
275 | ||
276 | attr_session->init_sysdir = 1; | |
277 | } | |
278 | ||
279 | if (attr_session->sysdir.size == 0) | |
280 | return GIT_ENOTFOUND; | |
281 | ||
282 | /* We can safely provide a git_buf with no allocation (asize == 0) to | |
283 | * a consumer. This allows them to treat this as a regular `git_buf`, | |
284 | * but their call to `git_buf_free` will not attempt to free it. | |
285 | */ | |
286 | out->ptr = attr_session->sysdir.ptr; | |
287 | out->size = attr_session->sysdir.size; | |
288 | out->asize = 0; | |
289 | return 0; | |
290 | } | |
291 | ||
9f779aac | 292 | static int attr_setup(git_repository *repo, git_attr_session *attr_session) |
e3a2a04c RB |
293 | { |
294 | int error = 0; | |
295 | const char *workdir = git_repository_workdir(repo); | |
296 | git_index *idx = NULL; | |
297 | git_buf sys = GIT_BUF_INIT; | |
298 | ||
d4b1b767 | 299 | if (attr_session && attr_session->init_setup) |
9f779aac ET |
300 | return 0; |
301 | ||
e3a2a04c RB |
302 | if ((error = git_attr_cache__init(repo)) < 0) |
303 | return error; | |
304 | ||
305 | /* preload attribute files that could contain macros so the | |
306 | * definitions will be available for later file parsing | |
307 | */ | |
308 | ||
d4b1b767 ET |
309 | error = system_attr_file(&sys, repo, attr_session); |
310 | ||
311 | if (error == 0) | |
e3a2a04c | 312 | error = preload_attr_file( |
9f779aac | 313 | repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr); |
d4b1b767 ET |
314 | else if (error != GIT_ENOTFOUND) |
315 | return error; | |
316 | ||
317 | git_buf_free(&sys); | |
e3a2a04c RB |
318 | |
319 | if ((error = preload_attr_file( | |
9f779aac | 320 | repo, attr_session, GIT_ATTR_FILE__FROM_FILE, |
e3a2a04c RB |
321 | NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0) |
322 | return error; | |
323 | ||
324 | if ((error = preload_attr_file( | |
9f779aac | 325 | repo, attr_session, GIT_ATTR_FILE__FROM_FILE, |
e3a2a04c RB |
326 | git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0) |
327 | return error; | |
328 | ||
329 | if (workdir != NULL && | |
330 | (error = preload_attr_file( | |
9f779aac | 331 | repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) |
e3a2a04c RB |
332 | return error; |
333 | ||
334 | if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || | |
335 | (error = preload_attr_file( | |
9f779aac | 336 | repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) |
e3a2a04c RB |
337 | return error; |
338 | ||
9f779aac | 339 | if (attr_session) |
d4b1b767 | 340 | attr_session->init_setup = 1; |
9f779aac | 341 | |
e3a2a04c RB |
342 | return error; |
343 | } | |
344 | ||
73b51450 RB |
345 | int git_attr_add_macro( |
346 | git_repository *repo, | |
347 | const char *name, | |
348 | const char *values) | |
349 | { | |
350 | int error; | |
351 | git_attr_rule *macro = NULL; | |
19fa2bc1 | 352 | git_pool *pool; |
73b51450 | 353 | |
ac16bd0a | 354 | if ((error = git_attr_cache__init(repo)) < 0) |
e3a2a04c | 355 | return error; |
73b51450 RB |
356 | |
357 | macro = git__calloc(1, sizeof(git_attr_rule)); | |
0d0fa7c3 | 358 | GITERR_CHECK_ALLOC(macro); |
73b51450 | 359 | |
19fa2bc1 RB |
360 | pool = &git_repository_attr_cache(repo)->pool; |
361 | ||
362 | macro->match.pattern = git_pool_strdup(pool, name); | |
0d0fa7c3 | 363 | GITERR_CHECK_ALLOC(macro->match.pattern); |
73b51450 RB |
364 | |
365 | macro->match.length = strlen(macro->match.pattern); | |
366 | macro->match.flags = GIT_ATTR_FNMATCH_MACRO; | |
367 | ||
19fa2bc1 | 368 | error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); |
73b51450 | 369 | |
0d0fa7c3 | 370 | if (!error) |
73b51450 RB |
371 | error = git_attr_cache__insert_macro(repo, macro); |
372 | ||
0d0fa7c3 | 373 | if (error < 0) |
73b51450 | 374 | git_attr_rule__free(macro); |
73b51450 RB |
375 | |
376 | return error; | |
377 | } | |
378 | ||
0cfcff5d RB |
379 | typedef struct { |
380 | git_repository *repo; | |
9f779aac | 381 | git_attr_session *attr_session; |
f917481e RB |
382 | uint32_t flags; |
383 | const char *workdir; | |
384 | git_index *index; | |
0cfcff5d RB |
385 | git_vector *files; |
386 | } attr_walk_up_info; | |
387 | ||
7d490872 | 388 | static int attr_decide_sources( |
823c0e9c | 389 | uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs) |
f917481e RB |
390 | { |
391 | int count = 0; | |
392 | ||
393 | switch (flags & 0x03) { | |
394 | case GIT_ATTR_CHECK_FILE_THEN_INDEX: | |
395 | if (has_wd) | |
823c0e9c | 396 | srcs[count++] = GIT_ATTR_FILE__FROM_FILE; |
f917481e | 397 | if (has_index) |
823c0e9c | 398 | srcs[count++] = GIT_ATTR_FILE__FROM_INDEX; |
f917481e RB |
399 | break; |
400 | case GIT_ATTR_CHECK_INDEX_THEN_FILE: | |
401 | if (has_index) | |
823c0e9c | 402 | srcs[count++] = GIT_ATTR_FILE__FROM_INDEX; |
f917481e | 403 | if (has_wd) |
823c0e9c | 404 | srcs[count++] = GIT_ATTR_FILE__FROM_FILE; |
f917481e RB |
405 | break; |
406 | case GIT_ATTR_CHECK_INDEX_ONLY: | |
407 | if (has_index) | |
823c0e9c | 408 | srcs[count++] = GIT_ATTR_FILE__FROM_INDEX; |
f917481e RB |
409 | break; |
410 | } | |
411 | ||
412 | return count; | |
413 | } | |
414 | ||
7d490872 RB |
415 | static int push_attr_file( |
416 | git_repository *repo, | |
9f779aac | 417 | git_attr_session *attr_session, |
7d490872 | 418 | git_vector *list, |
823c0e9c | 419 | git_attr_file_source source, |
7d490872 RB |
420 | const char *base, |
421 | const char *filename) | |
422 | { | |
423 | int error = 0; | |
424 | git_attr_file *file = NULL; | |
425 | ||
9f779aac ET |
426 | error = git_attr_cache__get(&file, repo, attr_session, |
427 | source, base, filename, git_attr_file__parse_buffer); | |
428 | ||
2e9d813b RB |
429 | if (error < 0) |
430 | return error; | |
431 | ||
432 | if (file != NULL) { | |
433 | if ((error = git_vector_insert(list, file)) < 0) | |
434 | git_attr_file__free(file); | |
435 | } | |
7d490872 RB |
436 | |
437 | return error; | |
438 | } | |
439 | ||
bbb988a5 | 440 | static int push_one_attr(void *ref, const char *path) |
0cfcff5d | 441 | { |
f917481e | 442 | int error = 0, n_src, i; |
0cfcff5d | 443 | attr_walk_up_info *info = (attr_walk_up_info *)ref; |
823c0e9c | 444 | git_attr_file_source src[2]; |
f917481e | 445 | |
7d490872 | 446 | n_src = attr_decide_sources( |
f917481e RB |
447 | info->flags, info->workdir != NULL, info->index != NULL, src); |
448 | ||
449 | for (i = 0; !error && i < n_src; ++i) | |
9f779aac ET |
450 | error = push_attr_file(info->repo, info->attr_session, |
451 | info->files, src[i], path, GIT_ATTR_FILE); | |
f917481e | 452 | |
25e0b157 | 453 | return error; |
0cfcff5d RB |
454 | } |
455 | ||
40ed4990 RB |
456 | static void release_attr_files(git_vector *files) |
457 | { | |
458 | size_t i; | |
459 | git_attr_file *file; | |
460 | ||
461 | git_vector_foreach(files, i, file) { | |
462 | git_attr_file__free(file); | |
463 | files->contents[i] = NULL; | |
464 | } | |
465 | git_vector_free(files); | |
466 | } | |
467 | ||
ee1f0b1a | 468 | static int collect_attr_files( |
f917481e | 469 | git_repository *repo, |
9f779aac | 470 | git_attr_session *attr_session, |
f917481e RB |
471 | uint32_t flags, |
472 | const char *path, | |
473 | git_vector *files) | |
ee1f0b1a | 474 | { |
52e9120c | 475 | int error = 0; |
ee1f0b1a | 476 | git_buf dir = GIT_BUF_INIT; |
ee1f0b1a | 477 | const char *workdir = git_repository_workdir(repo); |
dab89f9b | 478 | attr_walk_up_info info = { NULL }; |
ee1f0b1a | 479 | |
9f779aac | 480 | if ((error = attr_setup(repo, attr_session)) < 0) |
2e9d813b | 481 | return error; |
ee1f0b1a | 482 | |
cba285d3 RB |
483 | /* Resolve path in a non-bare repo */ |
484 | if (workdir != NULL) | |
f917481e | 485 | error = git_path_find_dir(&dir, path, workdir); |
a2b4407d T |
486 | else |
487 | error = git_path_dirname_r(&dir, path); | |
ab43ad2f | 488 | if (error < 0) |
ee1f0b1a RB |
489 | goto cleanup; |
490 | ||
ee1f0b1a RB |
491 | /* in precendence order highest to lowest: |
492 | * - $GIT_DIR/info/attributes | |
493 | * - path components with .gitattributes | |
494 | * - config core.attributesfile | |
495 | * - $GIT_PREFIX/etc/gitattributes | |
496 | */ | |
497 | ||
f917481e | 498 | error = push_attr_file( |
9f779aac | 499 | repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, |
7d490872 | 500 | git_repository_path(repo), GIT_ATTR_FILE_INREPO); |
ab43ad2f | 501 | if (error < 0) |
ee1f0b1a RB |
502 | goto cleanup; |
503 | ||
9f779aac ET |
504 | info.repo = repo; |
505 | info.attr_session = attr_session; | |
f917481e RB |
506 | info.flags = flags; |
507 | info.workdir = workdir; | |
508 | if (git_repository_index__weakptr(&info.index, repo) < 0) | |
509 | giterr_clear(); /* no error even if there is no index */ | |
0cfcff5d | 510 | info.files = files; |
f917481e | 511 | |
4e964117 | 512 | if (!strcmp(dir.ptr, ".")) |
a2b4407d | 513 | error = push_one_attr(&info, ""); |
4e964117 | 514 | else |
a2b4407d | 515 | error = git_path_walk_up(&dir, workdir, push_one_attr, &info); |
a2b4407d | 516 | |
ab43ad2f | 517 | if (error < 0) |
ee1f0b1a RB |
518 | goto cleanup; |
519 | ||
95dfb031 | 520 | if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { |
f917481e | 521 | error = push_attr_file( |
9f779aac | 522 | repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, |
7d490872 | 523 | NULL, git_repository_attr_cache(repo)->cfg_attr_file); |
95dfb031 RB |
524 | if (error < 0) |
525 | goto cleanup; | |
ee1f0b1a RB |
526 | } |
527 | ||
f917481e | 528 | if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { |
d4b1b767 ET |
529 | error = system_attr_file(&dir, repo, attr_session); |
530 | ||
f917481e | 531 | if (!error) |
7d490872 | 532 | error = push_attr_file( |
9f779aac ET |
533 | repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, |
534 | NULL, dir.ptr); | |
d4b1b767 | 535 | else if (error == GIT_ENOTFOUND) |
f917481e RB |
536 | error = 0; |
537 | } | |
ee1f0b1a RB |
538 | |
539 | cleanup: | |
ab43ad2f | 540 | if (error < 0) |
40ed4990 | 541 | release_attr_files(files); |
ee1f0b1a RB |
542 | git_buf_free(&dir); |
543 | ||
544 | return error; | |
545 | } |