]>
Commit | Line | Data |
---|---|---|
44b1ff4c | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
44b1ff4c VM |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
eae0bfdc PP |
8 | #include "filter.h" |
9 | ||
44b1ff4c | 10 | #include "common.h" |
22a2d3d5 | 11 | #include "futils.h" |
44b1ff4c | 12 | #include "hash.h" |
c5266eba | 13 | #include "repository.h" |
0646634e | 14 | #include "global.h" |
2a7d224f | 15 | #include "git2/sys/filter.h" |
c5266eba | 16 | #include "git2/config.h" |
9587895f | 17 | #include "blob.h" |
974774c7 | 18 | #include "attr_file.h" |
2a7d224f | 19 | #include "array.h" |
44b1ff4c | 20 | |
570ba25c RB |
21 | struct git_filter_source { |
22 | git_repository *repo; | |
23 | const char *path; | |
24 | git_oid oid; /* zero if unknown (which is likely) */ | |
25 | uint16_t filemode; /* zero if unknown */ | |
2a7d224f | 26 | git_filter_mode_t mode; |
795eaccd | 27 | uint32_t flags; |
570ba25c RB |
28 | }; |
29 | ||
85d54812 | 30 | typedef struct { |
2eecc288 | 31 | const char *filter_name; |
85d54812 RB |
32 | git_filter *filter; |
33 | void *payload; | |
34 | } git_filter_entry; | |
35 | ||
36 | struct git_filter_list { | |
37 | git_array_t(git_filter_entry) filters; | |
85d54812 | 38 | git_filter_source source; |
646364e7 | 39 | git_buf *temp_buf; |
85d54812 RB |
40 | char path[GIT_FLEX_ARRAY]; |
41 | }; | |
42 | ||
43 | typedef struct { | |
5623e627 | 44 | char *filter_name; |
85d54812 | 45 | git_filter *filter; |
974774c7 | 46 | int priority; |
29e92d38 | 47 | int initialized; |
974774c7 RB |
48 | size_t nattrs, nmatches; |
49 | char *attrdata; | |
50 | const char *attrs[GIT_FLEX_ARRAY]; | |
85d54812 RB |
51 | } git_filter_def; |
52 | ||
974774c7 RB |
53 | static int filter_def_priority_cmp(const void *a, const void *b) |
54 | { | |
55 | int pa = ((const git_filter_def *)a)->priority; | |
56 | int pb = ((const git_filter_def *)b)->priority; | |
57 | return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; | |
58 | } | |
59 | ||
2ed855a9 ET |
60 | struct git_filter_registry { |
61 | git_rwlock lock; | |
0646634e | 62 | git_vector filters; |
974774c7 RB |
63 | }; |
64 | ||
2ed855a9 | 65 | static struct git_filter_registry filter_registry; |
0646634e | 66 | |
2ed855a9 | 67 | static void git_filter_global_shutdown(void); |
4b11f25a | 68 | |
0646634e | 69 | |
974774c7 RB |
70 | static int filter_def_scan_attrs( |
71 | git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) | |
72 | { | |
73 | const char *start, *scan = attr_str; | |
74 | int has_eq; | |
75 | ||
76 | *nattr = *nmatch = 0; | |
77 | ||
78 | if (!scan) | |
79 | return 0; | |
80 | ||
81 | while (*scan) { | |
82 | while (git__isspace(*scan)) scan++; | |
83 | ||
84 | for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { | |
85 | if (*scan == '=') | |
86 | has_eq = 1; | |
87 | } | |
88 | ||
89 | if (scan > start) { | |
90 | (*nattr)++; | |
4b11f25a | 91 | if (has_eq || *start == '-' || *start == '+' || *start == '!') |
974774c7 RB |
92 | (*nmatch)++; |
93 | ||
94 | if (has_eq) | |
95 | git_buf_putc(attrs, '='); | |
96 | git_buf_put(attrs, start, scan - start); | |
97 | git_buf_putc(attrs, '\0'); | |
98 | } | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static void filter_def_set_attrs(git_filter_def *fdef) | |
105 | { | |
106 | char *scan = fdef->attrdata; | |
107 | size_t i; | |
108 | ||
109 | for (i = 0; i < fdef->nattrs; ++i) { | |
110 | const char *name, *value; | |
111 | ||
112 | switch (*scan) { | |
113 | case '=': | |
114 | name = scan + 1; | |
115 | for (scan++; *scan != '='; scan++) /* find '=' */; | |
116 | *scan++ = '\0'; | |
117 | value = scan; | |
118 | break; | |
119 | case '-': | |
120 | name = scan + 1; value = git_attr__false; break; | |
121 | case '+': | |
122 | name = scan + 1; value = git_attr__true; break; | |
123 | case '!': | |
124 | name = scan + 1; value = git_attr__unset; break; | |
125 | default: | |
126 | name = scan; value = NULL; break; | |
127 | } | |
128 | ||
129 | fdef->attrs[i] = name; | |
130 | fdef->attrs[i + fdef->nattrs] = value; | |
131 | ||
132 | scan += strlen(scan) + 1; | |
133 | } | |
134 | } | |
135 | ||
0646634e RB |
136 | static int filter_def_name_key_check(const void *key, const void *fdef) |
137 | { | |
138 | const char *name = | |
139 | fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; | |
40cb40fa RB |
140 | return name ? git__strcmp(key, name) : -1; |
141 | } | |
142 | ||
143 | static int filter_def_filter_key_check(const void *key, const void *fdef) | |
144 | { | |
145 | const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; | |
146 | return (key == filter) ? 0 : -1; | |
0646634e RB |
147 | } |
148 | ||
2ed855a9 ET |
149 | /* Note: callers must lock the registry before calling this function */ |
150 | static int filter_registry_insert( | |
974774c7 RB |
151 | const char *name, git_filter *filter, int priority) |
152 | { | |
153 | git_filter_def *fdef; | |
392702ee | 154 | size_t nattr = 0, nmatch = 0, alloc_len; |
974774c7 RB |
155 | git_buf attrs = GIT_BUF_INIT; |
156 | ||
974774c7 RB |
157 | if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) |
158 | return -1; | |
159 | ||
ac3d33df JK |
160 | GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2); |
161 | GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *)); | |
162 | GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def)); | |
392702ee ET |
163 | |
164 | fdef = git__calloc(1, alloc_len); | |
ac3d33df | 165 | GIT_ERROR_CHECK_ALLOC(fdef); |
974774c7 | 166 | |
5623e627 | 167 | fdef->filter_name = git__strdup(name); |
ac3d33df | 168 | GIT_ERROR_CHECK_ALLOC(fdef->filter_name); |
5623e627 | 169 | |
974774c7 RB |
170 | fdef->filter = filter; |
171 | fdef->priority = priority; | |
172 | fdef->nattrs = nattr; | |
173 | fdef->nmatches = nmatch; | |
174 | fdef->attrdata = git_buf_detach(&attrs); | |
175 | ||
176 | filter_def_set_attrs(fdef); | |
177 | ||
2ed855a9 | 178 | if (git_vector_insert(&filter_registry.filters, fdef) < 0) { |
5623e627 | 179 | git__free(fdef->filter_name); |
974774c7 RB |
180 | git__free(fdef->attrdata); |
181 | git__free(fdef); | |
182 | return -1; | |
183 | } | |
184 | ||
2ed855a9 | 185 | git_vector_sort(&filter_registry.filters); |
29e92d38 RB |
186 | return 0; |
187 | } | |
188 | ||
2ed855a9 ET |
189 | int git_filter_global_init(void) |
190 | { | |
191 | git_filter *crlf = NULL, *ident = NULL; | |
192 | int error = 0; | |
193 | ||
194 | if (git_rwlock_init(&filter_registry.lock) < 0) | |
195 | return -1; | |
196 | ||
197 | if ((error = git_vector_init(&filter_registry.filters, 2, | |
198 | filter_def_priority_cmp)) < 0) | |
199 | goto done; | |
200 | ||
201 | if ((crlf = git_crlf_filter_new()) == NULL || | |
202 | filter_registry_insert( | |
203 | GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || | |
204 | (ident = git_ident_filter_new()) == NULL || | |
205 | filter_registry_insert( | |
206 | GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) | |
207 | error = -1; | |
208 | ||
209 | git__on_shutdown(git_filter_global_shutdown); | |
210 | ||
211 | done: | |
212 | if (error) { | |
213 | git_filter_free(crlf); | |
214 | git_filter_free(ident); | |
215 | } | |
216 | ||
217 | return error; | |
218 | } | |
219 | ||
220 | static void git_filter_global_shutdown(void) | |
221 | { | |
222 | size_t pos; | |
223 | git_filter_def *fdef; | |
224 | ||
225 | if (git_rwlock_wrlock(&filter_registry.lock) < 0) | |
226 | return; | |
227 | ||
228 | git_vector_foreach(&filter_registry.filters, pos, fdef) { | |
229 | if (fdef->filter && fdef->filter->shutdown) { | |
230 | fdef->filter->shutdown(fdef->filter); | |
231 | fdef->initialized = false; | |
232 | } | |
233 | ||
234 | git__free(fdef->filter_name); | |
235 | git__free(fdef->attrdata); | |
236 | git__free(fdef); | |
237 | } | |
238 | ||
239 | git_vector_free(&filter_registry.filters); | |
240 | ||
241 | git_rwlock_wrunlock(&filter_registry.lock); | |
242 | git_rwlock_free(&filter_registry.lock); | |
243 | } | |
244 | ||
245 | /* Note: callers must lock the registry before calling this function */ | |
246 | static int filter_registry_find(size_t *pos, const char *name) | |
247 | { | |
248 | return git_vector_search2( | |
249 | pos, &filter_registry.filters, filter_def_name_key_check, name); | |
250 | } | |
251 | ||
252 | /* Note: callers must lock the registry before calling this function */ | |
253 | static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) | |
254 | { | |
255 | git_filter_def *fdef = NULL; | |
256 | ||
257 | if (!filter_registry_find(pos, name)) | |
258 | fdef = git_vector_get(&filter_registry.filters, *pos); | |
259 | ||
260 | return fdef; | |
261 | } | |
262 | ||
263 | ||
264 | int git_filter_register( | |
265 | const char *name, git_filter *filter, int priority) | |
266 | { | |
267 | int error; | |
268 | ||
269 | assert(name && filter); | |
270 | ||
271 | if (git_rwlock_wrlock(&filter_registry.lock) < 0) { | |
ac3d33df | 272 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
2ed855a9 ET |
273 | return -1; |
274 | } | |
275 | ||
276 | if (!filter_registry_find(NULL, name)) { | |
ac3d33df JK |
277 | git_error_set( |
278 | GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name); | |
2ed855a9 ET |
279 | error = GIT_EEXISTS; |
280 | goto done; | |
281 | } | |
282 | ||
283 | error = filter_registry_insert(name, filter, priority); | |
284 | ||
285 | done: | |
286 | git_rwlock_wrunlock(&filter_registry.lock); | |
287 | return error; | |
288 | } | |
289 | ||
974774c7 RB |
290 | int git_filter_unregister(const char *name) |
291 | { | |
292 | size_t pos; | |
293 | git_filter_def *fdef; | |
2ed855a9 | 294 | int error = 0; |
974774c7 | 295 | |
5623e627 AGO |
296 | assert(name); |
297 | ||
974774c7 | 298 | /* cannot unregister default filters */ |
eefc32d5 | 299 | if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { |
ac3d33df | 300 | git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name); |
974774c7 RB |
301 | return -1; |
302 | } | |
303 | ||
2ed855a9 | 304 | if (git_rwlock_wrlock(&filter_registry.lock) < 0) { |
ac3d33df | 305 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
2ed855a9 ET |
306 | return -1; |
307 | } | |
308 | ||
0646634e | 309 | if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { |
ac3d33df | 310 | git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name); |
2ed855a9 ET |
311 | error = GIT_ENOTFOUND; |
312 | goto done; | |
974774c7 RB |
313 | } |
314 | ||
2ed855a9 | 315 | git_vector_remove(&filter_registry.filters, pos); |
974774c7 | 316 | |
29e92d38 | 317 | if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { |
974774c7 | 318 | fdef->filter->shutdown(fdef->filter); |
29e92d38 RB |
319 | fdef->initialized = false; |
320 | } | |
974774c7 | 321 | |
5623e627 | 322 | git__free(fdef->filter_name); |
974774c7 RB |
323 | git__free(fdef->attrdata); |
324 | git__free(fdef); | |
325 | ||
2ed855a9 ET |
326 | done: |
327 | git_rwlock_wrunlock(&filter_registry.lock); | |
328 | return error; | |
974774c7 RB |
329 | } |
330 | ||
0646634e RB |
331 | static int filter_initialize(git_filter_def *fdef) |
332 | { | |
333 | int error = 0; | |
334 | ||
2ed855a9 ET |
335 | if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { |
336 | if ((error = fdef->filter->initialize(fdef->filter)) < 0) | |
337 | return error; | |
0646634e RB |
338 | } |
339 | ||
340 | fdef->initialized = true; | |
341 | return 0; | |
342 | } | |
343 | ||
974774c7 RB |
344 | git_filter *git_filter_lookup(const char *name) |
345 | { | |
346 | size_t pos; | |
0646634e | 347 | git_filter_def *fdef; |
2ed855a9 | 348 | git_filter *filter = NULL; |
0646634e | 349 | |
2ed855a9 | 350 | if (git_rwlock_rdlock(&filter_registry.lock) < 0) { |
ac3d33df | 351 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
0646634e | 352 | return NULL; |
2ed855a9 | 353 | } |
29e92d38 | 354 | |
2ed855a9 ET |
355 | if ((fdef = filter_registry_lookup(&pos, name)) == NULL || |
356 | (!fdef->initialized && filter_initialize(fdef) < 0)) | |
357 | goto done; | |
29e92d38 | 358 | |
2ed855a9 | 359 | filter = fdef->filter; |
29e92d38 | 360 | |
2ed855a9 ET |
361 | done: |
362 | git_rwlock_rdunlock(&filter_registry.lock); | |
363 | return filter; | |
974774c7 RB |
364 | } |
365 | ||
4b11f25a RB |
366 | void git_filter_free(git_filter *filter) |
367 | { | |
368 | git__free(filter); | |
369 | } | |
370 | ||
570ba25c RB |
371 | git_repository *git_filter_source_repo(const git_filter_source *src) |
372 | { | |
373 | return src->repo; | |
374 | } | |
375 | ||
376 | const char *git_filter_source_path(const git_filter_source *src) | |
377 | { | |
378 | return src->path; | |
379 | } | |
380 | ||
381 | uint16_t git_filter_source_filemode(const git_filter_source *src) | |
382 | { | |
383 | return src->filemode; | |
384 | } | |
385 | ||
386 | const git_oid *git_filter_source_id(const git_filter_source *src) | |
387 | { | |
22a2d3d5 | 388 | return git_oid_is_zero(&src->oid) ? NULL : &src->oid; |
570ba25c RB |
389 | } |
390 | ||
2a7d224f RB |
391 | git_filter_mode_t git_filter_source_mode(const git_filter_source *src) |
392 | { | |
393 | return src->mode; | |
394 | } | |
395 | ||
795eaccd | 396 | uint32_t git_filter_source_flags(const git_filter_source *src) |
5269008c | 397 | { |
795eaccd | 398 | return src->flags; |
5269008c RB |
399 | } |
400 | ||
40cb40fa | 401 | static int filter_list_new( |
2a7d224f | 402 | git_filter_list **out, const git_filter_source *src) |
27950fa3 | 403 | { |
85d54812 | 404 | git_filter_list *fl = NULL; |
f1453c59 | 405 | size_t pathlen = src->path ? strlen(src->path) : 0, alloclen; |
85d54812 | 406 | |
ac3d33df JK |
407 | GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen); |
408 | GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); | |
392702ee | 409 | |
f1453c59 | 410 | fl = git__calloc(1, alloclen); |
ac3d33df | 411 | GIT_ERROR_CHECK_ALLOC(fl); |
85d54812 | 412 | |
85d54812 RB |
413 | if (src->path) |
414 | memcpy(fl->path, src->path, pathlen); | |
415 | fl->source.repo = src->repo; | |
416 | fl->source.path = fl->path; | |
2a7d224f | 417 | fl->source.mode = src->mode; |
795eaccd | 418 | fl->source.flags = src->flags; |
85d54812 RB |
419 | |
420 | *out = fl; | |
421 | return 0; | |
422 | } | |
423 | ||
974774c7 | 424 | static int filter_list_check_attributes( |
9f779aac ET |
425 | const char ***out, |
426 | git_repository *repo, | |
427 | git_attr_session *attr_session, | |
428 | git_filter_def *fdef, | |
429 | const git_filter_source *src) | |
974774c7 | 430 | { |
974774c7 | 431 | const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); |
22a2d3d5 UG |
432 | uint32_t flags = 0; |
433 | size_t i; | |
434 | int error; | |
435 | ||
ac3d33df | 436 | GIT_ERROR_CHECK_ALLOC(strs); |
974774c7 | 437 | |
22a2d3d5 UG |
438 | if ((src->flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) |
439 | flags |= GIT_ATTR_CHECK_NO_SYSTEM; | |
440 | ||
441 | if ((src->flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) | |
442 | flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; | |
443 | ||
9f779aac | 444 | error = git_attr_get_many_with_session( |
22a2d3d5 | 445 | strs, repo, attr_session, flags, src->path, fdef->nattrs, fdef->attrs); |
974774c7 RB |
446 | |
447 | /* if no values were found but no matches are needed, it's okay! */ | |
448 | if (error == GIT_ENOTFOUND && !fdef->nmatches) { | |
ac3d33df | 449 | git_error_clear(); |
e399c7ee | 450 | git__free((void *)strs); |
974774c7 RB |
451 | return 0; |
452 | } | |
453 | ||
454 | for (i = 0; !error && i < fdef->nattrs; ++i) { | |
455 | const char *want = fdef->attrs[fdef->nattrs + i]; | |
22a2d3d5 | 456 | git_attr_value_t want_type, found_type; |
974774c7 RB |
457 | |
458 | if (!want) | |
459 | continue; | |
460 | ||
461 | want_type = git_attr_value(want); | |
462 | found_type = git_attr_value(strs[i]); | |
463 | ||
63924435 ET |
464 | if (want_type != found_type) |
465 | error = GIT_ENOTFOUND; | |
22a2d3d5 | 466 | else if (want_type == GIT_ATTR_VALUE_STRING && |
63924435 ET |
467 | strcmp(want, strs[i]) && |
468 | strcmp(want, "*")) | |
974774c7 RB |
469 | error = GIT_ENOTFOUND; |
470 | } | |
471 | ||
472 | if (error) | |
e399c7ee | 473 | git__free((void *)strs); |
974774c7 RB |
474 | else |
475 | *out = strs; | |
476 | ||
477 | return error; | |
478 | } | |
479 | ||
40cb40fa | 480 | int git_filter_list_new( |
5269008c RB |
481 | git_filter_list **out, |
482 | git_repository *repo, | |
483 | git_filter_mode_t mode, | |
795eaccd | 484 | uint32_t flags) |
40cb40fa RB |
485 | { |
486 | git_filter_source src = { 0 }; | |
487 | src.repo = repo; | |
488 | src.path = NULL; | |
489 | src.mode = mode; | |
795eaccd | 490 | src.flags = flags; |
40cb40fa RB |
491 | return filter_list_new(out, &src); |
492 | } | |
493 | ||
d05218b0 | 494 | int git_filter_list__load_ext( |
85d54812 RB |
495 | git_filter_list **filters, |
496 | git_repository *repo, | |
4b11f25a | 497 | git_blob *blob, /* can be NULL */ |
85d54812 | 498 | const char *path, |
5269008c | 499 | git_filter_mode_t mode, |
d05218b0 | 500 | git_filter_options *filter_opts) |
85d54812 RB |
501 | { |
502 | int error = 0; | |
503 | git_filter_list *fl = NULL; | |
504 | git_filter_source src = { 0 }; | |
505 | git_filter_entry *fe; | |
974774c7 RB |
506 | size_t idx; |
507 | git_filter_def *fdef; | |
85d54812 | 508 | |
2ed855a9 | 509 | if (git_rwlock_rdlock(&filter_registry.lock) < 0) { |
ac3d33df | 510 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
85d54812 | 511 | return -1; |
2ed855a9 | 512 | } |
85d54812 RB |
513 | |
514 | src.repo = repo; | |
515 | src.path = path; | |
2a7d224f | 516 | src.mode = mode; |
d05218b0 ET |
517 | src.flags = filter_opts->flags; |
518 | ||
4b11f25a RB |
519 | if (blob) |
520 | git_oid_cpy(&src.oid, git_blob_id(blob)); | |
85d54812 | 521 | |
2ed855a9 | 522 | git_vector_foreach(&filter_registry.filters, idx, fdef) { |
974774c7 | 523 | const char **values = NULL; |
85d54812 | 524 | void *payload = NULL; |
85d54812 RB |
525 | |
526 | if (!fdef || !fdef->filter) | |
527 | continue; | |
27950fa3 | 528 | |
974774c7 | 529 | if (fdef->nattrs > 0) { |
9f779aac | 530 | error = filter_list_check_attributes( |
d05218b0 | 531 | &values, repo, filter_opts->attr_session, fdef, &src); |
9f779aac | 532 | |
974774c7 RB |
533 | if (error == GIT_ENOTFOUND) { |
534 | error = 0; | |
535 | continue; | |
536 | } else if (error < 0) | |
537 | break; | |
538 | } | |
539 | ||
29e92d38 RB |
540 | if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) |
541 | break; | |
542 | ||
85d54812 | 543 | if (fdef->filter->check) |
974774c7 | 544 | error = fdef->filter->check( |
2a7d224f | 545 | fdef->filter, &payload, &src, values); |
974774c7 | 546 | |
e399c7ee | 547 | git__free((void *)values); |
85d54812 | 548 | |
eefc32d5 | 549 | if (error == GIT_PASSTHROUGH) |
85d54812 RB |
550 | error = 0; |
551 | else if (error < 0) | |
552 | break; | |
553 | else { | |
9c9aa1ba ET |
554 | if (!fl) { |
555 | if ((error = filter_list_new(&fl, &src)) < 0) | |
2ed855a9 | 556 | break; |
9c9aa1ba ET |
557 | |
558 | fl->temp_buf = filter_opts->temp_buf; | |
559 | } | |
85d54812 RB |
560 | |
561 | fe = git_array_alloc(fl->filters); | |
ac3d33df | 562 | GIT_ERROR_CHECK_ALLOC(fe); |
2eecc288 ET |
563 | |
564 | fe->filter = fdef->filter; | |
565 | fe->filter_name = fdef->filter_name; | |
85d54812 RB |
566 | fe->payload = payload; |
567 | } | |
568 | } | |
569 | ||
2ed855a9 ET |
570 | git_rwlock_rdunlock(&filter_registry.lock); |
571 | ||
85d54812 RB |
572 | if (error && fl != NULL) { |
573 | git_array_clear(fl->filters); | |
574 | git__free(fl); | |
575 | fl = NULL; | |
576 | } | |
577 | ||
578 | *filters = fl; | |
579 | return error; | |
580 | } | |
581 | ||
9f779aac ET |
582 | int git_filter_list_load( |
583 | git_filter_list **filters, | |
584 | git_repository *repo, | |
585 | git_blob *blob, /* can be NULL */ | |
586 | const char *path, | |
587 | git_filter_mode_t mode, | |
795eaccd | 588 | uint32_t flags) |
9f779aac | 589 | { |
d05218b0 ET |
590 | git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; |
591 | ||
592 | filter_opts.flags = flags; | |
593 | ||
594 | return git_filter_list__load_ext( | |
595 | filters, repo, blob, path, mode, &filter_opts); | |
9f779aac ET |
596 | } |
597 | ||
85d54812 RB |
598 | void git_filter_list_free(git_filter_list *fl) |
599 | { | |
600 | uint32_t i; | |
601 | ||
602 | if (!fl) | |
603 | return; | |
604 | ||
605 | for (i = 0; i < git_array_size(fl->filters); ++i) { | |
606 | git_filter_entry *fe = git_array_get(fl->filters, i); | |
607 | if (fe->filter->cleanup) | |
608 | fe->filter->cleanup(fe->filter, fe->payload); | |
27950fa3 VM |
609 | } |
610 | ||
85d54812 RB |
611 | git_array_clear(fl->filters); |
612 | git__free(fl); | |
27950fa3 VM |
613 | } |
614 | ||
2eecc288 ET |
615 | int git_filter_list_contains( |
616 | git_filter_list *fl, | |
617 | const char *name) | |
618 | { | |
619 | size_t i; | |
620 | ||
621 | assert(name); | |
622 | ||
623 | if (!fl) | |
624 | return 0; | |
625 | ||
626 | for (i = 0; i < fl->filters.size; i++) { | |
627 | if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) | |
628 | return 1; | |
629 | } | |
630 | ||
631 | return 0; | |
632 | } | |
633 | ||
40cb40fa RB |
634 | int git_filter_list_push( |
635 | git_filter_list *fl, git_filter *filter, void *payload) | |
636 | { | |
637 | int error = 0; | |
638 | size_t pos; | |
2ed855a9 | 639 | git_filter_def *fdef = NULL; |
40cb40fa RB |
640 | git_filter_entry *fe; |
641 | ||
642 | assert(fl && filter); | |
643 | ||
2ed855a9 | 644 | if (git_rwlock_rdlock(&filter_registry.lock) < 0) { |
ac3d33df | 645 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
2ed855a9 ET |
646 | return -1; |
647 | } | |
648 | ||
40cb40fa | 649 | if (git_vector_search2( |
2ed855a9 ET |
650 | &pos, &filter_registry.filters, |
651 | filter_def_filter_key_check, filter) == 0) | |
652 | fdef = git_vector_get(&filter_registry.filters, pos); | |
653 | ||
654 | git_rwlock_rdunlock(&filter_registry.lock); | |
655 | ||
656 | if (fdef == NULL) { | |
ac3d33df | 657 | git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); |
40cb40fa RB |
658 | return -1; |
659 | } | |
660 | ||
40cb40fa RB |
661 | if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) |
662 | return error; | |
663 | ||
664 | fe = git_array_alloc(fl->filters); | |
ac3d33df | 665 | GIT_ERROR_CHECK_ALLOC(fe); |
40cb40fa RB |
666 | fe->filter = filter; |
667 | fe->payload = payload; | |
668 | ||
669 | return 0; | |
670 | } | |
671 | ||
b47349b8 RB |
672 | size_t git_filter_list_length(const git_filter_list *fl) |
673 | { | |
674 | return fl ? git_array_size(fl->filters) : 0; | |
675 | } | |
676 | ||
5555696f | 677 | struct buf_stream { |
f7c0125f | 678 | git_writestream parent; |
5555696f ET |
679 | git_buf *target; |
680 | bool complete; | |
681 | }; | |
682 | ||
683 | static int buf_stream_write( | |
b75f15aa | 684 | git_writestream *s, const char *buffer, size_t len) |
44b1ff4c | 685 | { |
5555696f ET |
686 | struct buf_stream *buf_stream = (struct buf_stream *)s; |
687 | assert(buf_stream); | |
44b1ff4c | 688 | |
5555696f | 689 | assert(buf_stream->complete == 0); |
1e4976cb | 690 | |
5555696f ET |
691 | return git_buf_put(buf_stream->target, buffer, len); |
692 | } | |
44b1ff4c | 693 | |
b75f15aa | 694 | static int buf_stream_close(git_writestream *s) |
5555696f ET |
695 | { |
696 | struct buf_stream *buf_stream = (struct buf_stream *)s; | |
697 | assert(buf_stream); | |
44b1ff4c | 698 | |
5555696f ET |
699 | assert(buf_stream->complete == 0); |
700 | buf_stream->complete = 1; | |
44b1ff4c | 701 | |
5555696f ET |
702 | return 0; |
703 | } | |
85d54812 | 704 | |
b75f15aa | 705 | static void buf_stream_free(git_writestream *s) |
5555696f ET |
706 | { |
707 | GIT_UNUSED(s); | |
708 | } | |
85d54812 | 709 | |
5555696f ET |
710 | static void buf_stream_init(struct buf_stream *writer, git_buf *target) |
711 | { | |
712 | memset(writer, 0, sizeof(struct buf_stream)); | |
44b1ff4c | 713 | |
f7c0125f ET |
714 | writer->parent.write = buf_stream_write; |
715 | writer->parent.close = buf_stream_close; | |
716 | writer->parent.free = buf_stream_free; | |
5555696f | 717 | writer->target = target; |
2a7d224f | 718 | |
5555696f ET |
719 | git_buf_clear(target); |
720 | } | |
44b1ff4c | 721 | |
5555696f ET |
722 | int git_filter_list_apply_to_data( |
723 | git_buf *tgt, git_filter_list *filters, git_buf *src) | |
724 | { | |
725 | struct buf_stream writer; | |
726 | int error; | |
727 | ||
728 | git_buf_sanitize(tgt); | |
729 | git_buf_sanitize(src); | |
730 | ||
d4cf1675 ET |
731 | if (!filters) { |
732 | git_buf_attach_notowned(tgt, src->ptr, src->size); | |
733 | return 0; | |
734 | } | |
5555696f ET |
735 | |
736 | buf_stream_init(&writer, tgt); | |
737 | ||
738 | if ((error = git_filter_list_stream_data(filters, src, | |
69f0032b | 739 | &writer.parent)) < 0) |
5555696f ET |
740 | return error; |
741 | ||
742 | assert(writer.complete); | |
424222f4 | 743 | return error; |
44b1ff4c | 744 | } |
2a7d224f RB |
745 | |
746 | int git_filter_list_apply_to_file( | |
a9f51e43 | 747 | git_buf *out, |
2a7d224f RB |
748 | git_filter_list *filters, |
749 | git_repository *repo, | |
750 | const char *path) | |
751 | { | |
5555696f | 752 | struct buf_stream writer; |
2a7d224f | 753 | int error; |
2a7d224f | 754 | |
5555696f | 755 | buf_stream_init(&writer, out); |
2a7d224f | 756 | |
5555696f | 757 | if ((error = git_filter_list_stream_file( |
69f0032b | 758 | filters, repo, path, &writer.parent)) < 0) |
5555696f | 759 | return error; |
2a7d224f | 760 | |
5555696f | 761 | assert(writer.complete); |
2a7d224f RB |
762 | return error; |
763 | } | |
764 | ||
fbdc9db3 ET |
765 | static int buf_from_blob(git_buf *out, git_blob *blob) |
766 | { | |
22a2d3d5 | 767 | git_object_size_t rawsize = git_blob_rawsize(blob); |
fbdc9db3 ET |
768 | |
769 | if (!git__is_sizet(rawsize)) { | |
ac3d33df | 770 | git_error_set(GIT_ERROR_OS, "blob is too large to filter"); |
fbdc9db3 ET |
771 | return -1; |
772 | } | |
773 | ||
d4cf1675 | 774 | git_buf_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); |
fbdc9db3 ET |
775 | return 0; |
776 | } | |
777 | ||
2a7d224f | 778 | int git_filter_list_apply_to_blob( |
a9f51e43 | 779 | git_buf *out, |
2a7d224f RB |
780 | git_filter_list *filters, |
781 | git_blob *blob) | |
782 | { | |
5555696f ET |
783 | struct buf_stream writer; |
784 | int error; | |
71379313 | 785 | |
5555696f | 786 | buf_stream_init(&writer, out); |
fbdc9db3 | 787 | |
5555696f | 788 | if ((error = git_filter_list_stream_blob( |
69f0032b | 789 | filters, blob, &writer.parent)) < 0) |
5555696f | 790 | return error; |
fbdc9db3 | 791 | |
5555696f ET |
792 | assert(writer.complete); |
793 | return error; | |
fbdc9db3 ET |
794 | } |
795 | ||
796 | struct proxy_stream { | |
f7c0125f | 797 | git_writestream parent; |
fbdc9db3 ET |
798 | git_filter *filter; |
799 | const git_filter_source *source; | |
800 | void **payload; | |
801 | git_buf input; | |
646364e7 ET |
802 | git_buf temp_buf; |
803 | git_buf *output; | |
b75f15aa | 804 | git_writestream *target; |
fbdc9db3 ET |
805 | }; |
806 | ||
807 | static int proxy_stream_write( | |
b75f15aa | 808 | git_writestream *s, const char *buffer, size_t len) |
fbdc9db3 ET |
809 | { |
810 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
811 | assert(proxy_stream); | |
812 | ||
813 | return git_buf_put(&proxy_stream->input, buffer, len); | |
814 | } | |
815 | ||
b75f15aa | 816 | static int proxy_stream_close(git_writestream *s) |
fbdc9db3 ET |
817 | { |
818 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
819 | git_buf *writebuf; | |
ac3d33df | 820 | git_error_state error_state = {0}; |
fbdc9db3 ET |
821 | int error; |
822 | ||
823 | assert(proxy_stream); | |
824 | ||
825 | error = proxy_stream->filter->apply( | |
826 | proxy_stream->filter, | |
827 | proxy_stream->payload, | |
646364e7 | 828 | proxy_stream->output, |
fbdc9db3 ET |
829 | &proxy_stream->input, |
830 | proxy_stream->source); | |
831 | ||
832 | if (error == GIT_PASSTHROUGH) { | |
833 | writebuf = &proxy_stream->input; | |
834 | } else if (error == 0) { | |
646364e7 ET |
835 | git_buf_sanitize(proxy_stream->output); |
836 | writebuf = proxy_stream->output; | |
fbdc9db3 | 837 | } else { |
ac3d33df JK |
838 | /* close stream before erroring out taking care |
839 | * to preserve the original error */ | |
840 | git_error_state_capture(&error_state, error); | |
841 | proxy_stream->target->close(proxy_stream->target); | |
842 | git_error_state_restore(&error_state); | |
fbdc9db3 ET |
843 | return error; |
844 | } | |
845 | ||
846 | if ((error = proxy_stream->target->write( | |
847 | proxy_stream->target, writebuf->ptr, writebuf->size)) == 0) | |
848 | error = proxy_stream->target->close(proxy_stream->target); | |
849 | ||
850 | return error; | |
851 | } | |
852 | ||
b75f15aa | 853 | static void proxy_stream_free(git_writestream *s) |
fbdc9db3 ET |
854 | { |
855 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
856 | assert(proxy_stream); | |
857 | ||
ac3d33df JK |
858 | git_buf_dispose(&proxy_stream->input); |
859 | git_buf_dispose(&proxy_stream->temp_buf); | |
fbdc9db3 ET |
860 | git__free(proxy_stream); |
861 | } | |
862 | ||
863 | static int proxy_stream_init( | |
b75f15aa | 864 | git_writestream **out, |
fbdc9db3 | 865 | git_filter *filter, |
646364e7 | 866 | git_buf *temp_buf, |
fbdc9db3 ET |
867 | void **payload, |
868 | const git_filter_source *source, | |
b75f15aa | 869 | git_writestream *target) |
fbdc9db3 ET |
870 | { |
871 | struct proxy_stream *proxy_stream = git__calloc(1, sizeof(struct proxy_stream)); | |
ac3d33df | 872 | GIT_ERROR_CHECK_ALLOC(proxy_stream); |
fbdc9db3 | 873 | |
f7c0125f ET |
874 | proxy_stream->parent.write = proxy_stream_write; |
875 | proxy_stream->parent.close = proxy_stream_close; | |
876 | proxy_stream->parent.free = proxy_stream_free; | |
fbdc9db3 ET |
877 | proxy_stream->filter = filter; |
878 | proxy_stream->payload = payload; | |
879 | proxy_stream->source = source; | |
880 | proxy_stream->target = target; | |
646364e7 | 881 | proxy_stream->output = temp_buf ? temp_buf : &proxy_stream->temp_buf; |
fbdc9db3 | 882 | |
669ae274 ET |
883 | if (temp_buf) |
884 | git_buf_clear(temp_buf); | |
885 | ||
b75f15aa | 886 | *out = (git_writestream *)proxy_stream; |
fbdc9db3 ET |
887 | return 0; |
888 | } | |
889 | ||
890 | static int stream_list_init( | |
b75f15aa | 891 | git_writestream **out, |
fbdc9db3 ET |
892 | git_vector *streams, |
893 | git_filter_list *filters, | |
b75f15aa | 894 | git_writestream *target) |
fbdc9db3 | 895 | { |
b75f15aa | 896 | git_writestream *last_stream = target; |
fbdc9db3 ET |
897 | size_t i; |
898 | int error = 0; | |
899 | ||
900 | *out = NULL; | |
901 | ||
902 | if (!filters) { | |
903 | *out = target; | |
904 | return 0; | |
905 | } | |
906 | ||
907 | /* Create filters last to first to get the chaining direction */ | |
908 | for (i = 0; i < git_array_size(filters->filters); ++i) { | |
909 | size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? | |
910 | git_array_size(filters->filters) - 1 - i : i; | |
911 | git_filter_entry *fe = git_array_get(filters->filters, filter_idx); | |
b75f15aa | 912 | git_writestream *filter_stream; |
a78441bc | 913 | |
fbdc9db3 ET |
914 | assert(fe->filter->stream || fe->filter->apply); |
915 | ||
5555696f ET |
916 | /* If necessary, create a stream that proxies the traditional |
917 | * application. | |
918 | */ | |
646364e7 ET |
919 | if (fe->filter->stream) |
920 | error = fe->filter->stream(&filter_stream, fe->filter, | |
5555696f | 921 | &fe->payload, &filters->source, last_stream); |
646364e7 ET |
922 | else |
923 | /* Create a stream that proxies the one-shot apply */ | |
924 | error = proxy_stream_init(&filter_stream, fe->filter, | |
925 | filters->temp_buf, &fe->payload, &filters->source, | |
926 | last_stream); | |
fbdc9db3 | 927 | |
5555696f | 928 | if (error < 0) |
cf07db2f | 929 | goto out; |
5555696f ET |
930 | |
931 | git_vector_insert(streams, filter_stream); | |
fbdc9db3 | 932 | last_stream = filter_stream; |
71379313 RB |
933 | } |
934 | ||
cf07db2f PS |
935 | out: |
936 | if (error) | |
937 | last_stream->close(last_stream); | |
938 | else | |
939 | *out = last_stream; | |
940 | ||
941 | return error; | |
fbdc9db3 ET |
942 | } |
943 | ||
22a2d3d5 | 944 | static void filter_streams_free(git_vector *streams) |
fbdc9db3 | 945 | { |
b75f15aa | 946 | git_writestream *stream; |
fbdc9db3 ET |
947 | size_t i; |
948 | ||
949 | git_vector_foreach(streams, i, stream) | |
950 | stream->free(stream); | |
5555696f | 951 | git_vector_free(streams); |
fbdc9db3 ET |
952 | } |
953 | ||
fbdc9db3 ET |
954 | int git_filter_list_stream_file( |
955 | git_filter_list *filters, | |
fbdc9db3 ET |
956 | git_repository *repo, |
957 | const char *path, | |
b75f15aa | 958 | git_writestream *target) |
fbdc9db3 | 959 | { |
7dd22538 | 960 | char buf[FILTERIO_BUFSIZE]; |
5555696f | 961 | git_buf abspath = GIT_BUF_INIT; |
fbdc9db3 ET |
962 | const char *base = repo ? git_repository_workdir(repo) : NULL; |
963 | git_vector filter_streams = GIT_VECTOR_INIT; | |
b75f15aa | 964 | git_writestream *stream_start; |
fbdc9db3 | 965 | ssize_t readlen; |
cf07db2f | 966 | int fd = -1, error, initialized = 0; |
fbdc9db3 ET |
967 | |
968 | if ((error = stream_list_init( | |
969 | &stream_start, &filter_streams, filters, target)) < 0 || | |
970 | (error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0) | |
971 | goto done; | |
cf07db2f | 972 | initialized = 1; |
fbdc9db3 | 973 | |
6a2edc5a | 974 | if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { |
fbdc9db3 ET |
975 | error = fd; |
976 | goto done; | |
977 | } | |
978 | ||
7dd22538 | 979 | while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { |
5555696f | 980 | if ((error = stream_start->write(stream_start, buf, readlen)) < 0) |
fbdc9db3 ET |
981 | goto done; |
982 | } | |
983 | ||
cf07db2f | 984 | if (readlen < 0) |
ac3d33df | 985 | error = -1; |
fbdc9db3 | 986 | |
fbdc9db3 | 987 | done: |
cf07db2f PS |
988 | if (initialized) |
989 | error |= stream_start->close(stream_start); | |
990 | ||
0137aba5 CMN |
991 | if (fd >= 0) |
992 | p_close(fd); | |
22a2d3d5 | 993 | filter_streams_free(&filter_streams); |
ac3d33df | 994 | git_buf_dispose(&abspath); |
fbdc9db3 ET |
995 | return error; |
996 | } | |
997 | ||
998 | int git_filter_list_stream_data( | |
999 | git_filter_list *filters, | |
1000 | git_buf *data, | |
b75f15aa | 1001 | git_writestream *target) |
fbdc9db3 ET |
1002 | { |
1003 | git_vector filter_streams = GIT_VECTOR_INIT; | |
b75f15aa | 1004 | git_writestream *stream_start; |
cf07db2f | 1005 | int error, initialized = 0; |
fbdc9db3 | 1006 | |
5555696f ET |
1007 | git_buf_sanitize(data); |
1008 | ||
4de7f3bf CMN |
1009 | if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) |
1010 | goto out; | |
cf07db2f | 1011 | initialized = 1; |
fbdc9db3 | 1012 | |
cf07db2f PS |
1013 | if ((error = stream_start->write( |
1014 | stream_start, data->ptr, data->size)) < 0) | |
1015 | goto out; | |
4de7f3bf CMN |
1016 | |
1017 | out: | |
cf07db2f PS |
1018 | if (initialized) |
1019 | error |= stream_start->close(stream_start); | |
1020 | ||
22a2d3d5 | 1021 | filter_streams_free(&filter_streams); |
cf07db2f | 1022 | return error; |
fbdc9db3 ET |
1023 | } |
1024 | ||
1025 | int git_filter_list_stream_blob( | |
1026 | git_filter_list *filters, | |
1027 | git_blob *blob, | |
b75f15aa | 1028 | git_writestream *target) |
fbdc9db3 ET |
1029 | { |
1030 | git_buf in = GIT_BUF_INIT; | |
1031 | ||
1032 | if (buf_from_blob(&in, blob) < 0) | |
1033 | return -1; | |
2a7d224f | 1034 | |
4b11f25a RB |
1035 | if (filters) |
1036 | git_oid_cpy(&filters->source.oid, git_blob_id(blob)); | |
1037 | ||
fbdc9db3 | 1038 | return git_filter_list_stream_data(filters, &in, target); |
2a7d224f | 1039 | } |
a78441bc MM |
1040 | |
1041 | int git_filter_init(git_filter *filter, unsigned int version) | |
1042 | { | |
1043 | GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); | |
1044 | return 0; | |
1045 | } |