]>
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 VM |
10 | #include "common.h" |
11 | #include "fileops.h" | |
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 | { | |
388 | return git_oid_iszero(&src->oid) ? NULL : &src->oid; | |
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 RB |
430 | { |
431 | int error; | |
432 | size_t i; | |
433 | const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); | |
ac3d33df | 434 | GIT_ERROR_CHECK_ALLOC(strs); |
974774c7 | 435 | |
9f779aac ET |
436 | error = git_attr_get_many_with_session( |
437 | strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs); | |
974774c7 RB |
438 | |
439 | /* if no values were found but no matches are needed, it's okay! */ | |
440 | if (error == GIT_ENOTFOUND && !fdef->nmatches) { | |
ac3d33df | 441 | git_error_clear(); |
e399c7ee | 442 | git__free((void *)strs); |
974774c7 RB |
443 | return 0; |
444 | } | |
445 | ||
446 | for (i = 0; !error && i < fdef->nattrs; ++i) { | |
447 | const char *want = fdef->attrs[fdef->nattrs + i]; | |
448 | git_attr_t want_type, found_type; | |
449 | ||
450 | if (!want) | |
451 | continue; | |
452 | ||
453 | want_type = git_attr_value(want); | |
454 | found_type = git_attr_value(strs[i]); | |
455 | ||
63924435 ET |
456 | if (want_type != found_type) |
457 | error = GIT_ENOTFOUND; | |
458 | else if (want_type == GIT_ATTR_VALUE_T && | |
459 | strcmp(want, strs[i]) && | |
460 | strcmp(want, "*")) | |
974774c7 RB |
461 | error = GIT_ENOTFOUND; |
462 | } | |
463 | ||
464 | if (error) | |
e399c7ee | 465 | git__free((void *)strs); |
974774c7 RB |
466 | else |
467 | *out = strs; | |
468 | ||
469 | return error; | |
470 | } | |
471 | ||
40cb40fa | 472 | int git_filter_list_new( |
5269008c RB |
473 | git_filter_list **out, |
474 | git_repository *repo, | |
475 | git_filter_mode_t mode, | |
795eaccd | 476 | uint32_t flags) |
40cb40fa RB |
477 | { |
478 | git_filter_source src = { 0 }; | |
479 | src.repo = repo; | |
480 | src.path = NULL; | |
481 | src.mode = mode; | |
795eaccd | 482 | src.flags = flags; |
40cb40fa RB |
483 | return filter_list_new(out, &src); |
484 | } | |
485 | ||
d05218b0 | 486 | int git_filter_list__load_ext( |
85d54812 RB |
487 | git_filter_list **filters, |
488 | git_repository *repo, | |
4b11f25a | 489 | git_blob *blob, /* can be NULL */ |
85d54812 | 490 | const char *path, |
5269008c | 491 | git_filter_mode_t mode, |
d05218b0 | 492 | git_filter_options *filter_opts) |
85d54812 RB |
493 | { |
494 | int error = 0; | |
495 | git_filter_list *fl = NULL; | |
496 | git_filter_source src = { 0 }; | |
497 | git_filter_entry *fe; | |
974774c7 RB |
498 | size_t idx; |
499 | git_filter_def *fdef; | |
85d54812 | 500 | |
2ed855a9 | 501 | if (git_rwlock_rdlock(&filter_registry.lock) < 0) { |
ac3d33df | 502 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
85d54812 | 503 | return -1; |
2ed855a9 | 504 | } |
85d54812 RB |
505 | |
506 | src.repo = repo; | |
507 | src.path = path; | |
2a7d224f | 508 | src.mode = mode; |
d05218b0 ET |
509 | src.flags = filter_opts->flags; |
510 | ||
4b11f25a RB |
511 | if (blob) |
512 | git_oid_cpy(&src.oid, git_blob_id(blob)); | |
85d54812 | 513 | |
2ed855a9 | 514 | git_vector_foreach(&filter_registry.filters, idx, fdef) { |
974774c7 | 515 | const char **values = NULL; |
85d54812 | 516 | void *payload = NULL; |
85d54812 RB |
517 | |
518 | if (!fdef || !fdef->filter) | |
519 | continue; | |
27950fa3 | 520 | |
974774c7 | 521 | if (fdef->nattrs > 0) { |
9f779aac | 522 | error = filter_list_check_attributes( |
d05218b0 | 523 | &values, repo, filter_opts->attr_session, fdef, &src); |
9f779aac | 524 | |
974774c7 RB |
525 | if (error == GIT_ENOTFOUND) { |
526 | error = 0; | |
527 | continue; | |
528 | } else if (error < 0) | |
529 | break; | |
530 | } | |
531 | ||
29e92d38 RB |
532 | if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) |
533 | break; | |
534 | ||
85d54812 | 535 | if (fdef->filter->check) |
974774c7 | 536 | error = fdef->filter->check( |
2a7d224f | 537 | fdef->filter, &payload, &src, values); |
974774c7 | 538 | |
e399c7ee | 539 | git__free((void *)values); |
85d54812 | 540 | |
eefc32d5 | 541 | if (error == GIT_PASSTHROUGH) |
85d54812 RB |
542 | error = 0; |
543 | else if (error < 0) | |
544 | break; | |
545 | else { | |
9c9aa1ba ET |
546 | if (!fl) { |
547 | if ((error = filter_list_new(&fl, &src)) < 0) | |
2ed855a9 | 548 | break; |
9c9aa1ba ET |
549 | |
550 | fl->temp_buf = filter_opts->temp_buf; | |
551 | } | |
85d54812 RB |
552 | |
553 | fe = git_array_alloc(fl->filters); | |
ac3d33df | 554 | GIT_ERROR_CHECK_ALLOC(fe); |
2eecc288 ET |
555 | |
556 | fe->filter = fdef->filter; | |
557 | fe->filter_name = fdef->filter_name; | |
85d54812 RB |
558 | fe->payload = payload; |
559 | } | |
560 | } | |
561 | ||
2ed855a9 ET |
562 | git_rwlock_rdunlock(&filter_registry.lock); |
563 | ||
85d54812 RB |
564 | if (error && fl != NULL) { |
565 | git_array_clear(fl->filters); | |
566 | git__free(fl); | |
567 | fl = NULL; | |
568 | } | |
569 | ||
570 | *filters = fl; | |
571 | return error; | |
572 | } | |
573 | ||
9f779aac ET |
574 | int git_filter_list_load( |
575 | git_filter_list **filters, | |
576 | git_repository *repo, | |
577 | git_blob *blob, /* can be NULL */ | |
578 | const char *path, | |
579 | git_filter_mode_t mode, | |
795eaccd | 580 | uint32_t flags) |
9f779aac | 581 | { |
d05218b0 ET |
582 | git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; |
583 | ||
584 | filter_opts.flags = flags; | |
585 | ||
586 | return git_filter_list__load_ext( | |
587 | filters, repo, blob, path, mode, &filter_opts); | |
9f779aac ET |
588 | } |
589 | ||
85d54812 RB |
590 | void git_filter_list_free(git_filter_list *fl) |
591 | { | |
592 | uint32_t i; | |
593 | ||
594 | if (!fl) | |
595 | return; | |
596 | ||
597 | for (i = 0; i < git_array_size(fl->filters); ++i) { | |
598 | git_filter_entry *fe = git_array_get(fl->filters, i); | |
599 | if (fe->filter->cleanup) | |
600 | fe->filter->cleanup(fe->filter, fe->payload); | |
27950fa3 VM |
601 | } |
602 | ||
85d54812 RB |
603 | git_array_clear(fl->filters); |
604 | git__free(fl); | |
27950fa3 VM |
605 | } |
606 | ||
2eecc288 ET |
607 | int git_filter_list_contains( |
608 | git_filter_list *fl, | |
609 | const char *name) | |
610 | { | |
611 | size_t i; | |
612 | ||
613 | assert(name); | |
614 | ||
615 | if (!fl) | |
616 | return 0; | |
617 | ||
618 | for (i = 0; i < fl->filters.size; i++) { | |
619 | if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) | |
620 | return 1; | |
621 | } | |
622 | ||
623 | return 0; | |
624 | } | |
625 | ||
40cb40fa RB |
626 | int git_filter_list_push( |
627 | git_filter_list *fl, git_filter *filter, void *payload) | |
628 | { | |
629 | int error = 0; | |
630 | size_t pos; | |
2ed855a9 | 631 | git_filter_def *fdef = NULL; |
40cb40fa RB |
632 | git_filter_entry *fe; |
633 | ||
634 | assert(fl && filter); | |
635 | ||
2ed855a9 | 636 | if (git_rwlock_rdlock(&filter_registry.lock) < 0) { |
ac3d33df | 637 | git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); |
2ed855a9 ET |
638 | return -1; |
639 | } | |
640 | ||
40cb40fa | 641 | if (git_vector_search2( |
2ed855a9 ET |
642 | &pos, &filter_registry.filters, |
643 | filter_def_filter_key_check, filter) == 0) | |
644 | fdef = git_vector_get(&filter_registry.filters, pos); | |
645 | ||
646 | git_rwlock_rdunlock(&filter_registry.lock); | |
647 | ||
648 | if (fdef == NULL) { | |
ac3d33df | 649 | git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); |
40cb40fa RB |
650 | return -1; |
651 | } | |
652 | ||
40cb40fa RB |
653 | if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) |
654 | return error; | |
655 | ||
656 | fe = git_array_alloc(fl->filters); | |
ac3d33df | 657 | GIT_ERROR_CHECK_ALLOC(fe); |
40cb40fa RB |
658 | fe->filter = filter; |
659 | fe->payload = payload; | |
660 | ||
661 | return 0; | |
662 | } | |
663 | ||
b47349b8 RB |
664 | size_t git_filter_list_length(const git_filter_list *fl) |
665 | { | |
666 | return fl ? git_array_size(fl->filters) : 0; | |
667 | } | |
668 | ||
5555696f | 669 | struct buf_stream { |
f7c0125f | 670 | git_writestream parent; |
5555696f ET |
671 | git_buf *target; |
672 | bool complete; | |
673 | }; | |
674 | ||
675 | static int buf_stream_write( | |
b75f15aa | 676 | git_writestream *s, const char *buffer, size_t len) |
44b1ff4c | 677 | { |
5555696f ET |
678 | struct buf_stream *buf_stream = (struct buf_stream *)s; |
679 | assert(buf_stream); | |
44b1ff4c | 680 | |
5555696f | 681 | assert(buf_stream->complete == 0); |
1e4976cb | 682 | |
5555696f ET |
683 | return git_buf_put(buf_stream->target, buffer, len); |
684 | } | |
44b1ff4c | 685 | |
b75f15aa | 686 | static int buf_stream_close(git_writestream *s) |
5555696f ET |
687 | { |
688 | struct buf_stream *buf_stream = (struct buf_stream *)s; | |
689 | assert(buf_stream); | |
44b1ff4c | 690 | |
5555696f ET |
691 | assert(buf_stream->complete == 0); |
692 | buf_stream->complete = 1; | |
44b1ff4c | 693 | |
5555696f ET |
694 | return 0; |
695 | } | |
85d54812 | 696 | |
b75f15aa | 697 | static void buf_stream_free(git_writestream *s) |
5555696f ET |
698 | { |
699 | GIT_UNUSED(s); | |
700 | } | |
85d54812 | 701 | |
5555696f ET |
702 | static void buf_stream_init(struct buf_stream *writer, git_buf *target) |
703 | { | |
704 | memset(writer, 0, sizeof(struct buf_stream)); | |
44b1ff4c | 705 | |
f7c0125f ET |
706 | writer->parent.write = buf_stream_write; |
707 | writer->parent.close = buf_stream_close; | |
708 | writer->parent.free = buf_stream_free; | |
5555696f | 709 | writer->target = target; |
2a7d224f | 710 | |
5555696f ET |
711 | git_buf_clear(target); |
712 | } | |
44b1ff4c | 713 | |
5555696f ET |
714 | int git_filter_list_apply_to_data( |
715 | git_buf *tgt, git_filter_list *filters, git_buf *src) | |
716 | { | |
717 | struct buf_stream writer; | |
718 | int error; | |
719 | ||
720 | git_buf_sanitize(tgt); | |
721 | git_buf_sanitize(src); | |
722 | ||
d4cf1675 ET |
723 | if (!filters) { |
724 | git_buf_attach_notowned(tgt, src->ptr, src->size); | |
725 | return 0; | |
726 | } | |
5555696f ET |
727 | |
728 | buf_stream_init(&writer, tgt); | |
729 | ||
730 | if ((error = git_filter_list_stream_data(filters, src, | |
69f0032b | 731 | &writer.parent)) < 0) |
5555696f ET |
732 | return error; |
733 | ||
734 | assert(writer.complete); | |
424222f4 | 735 | return error; |
44b1ff4c | 736 | } |
2a7d224f RB |
737 | |
738 | int git_filter_list_apply_to_file( | |
a9f51e43 | 739 | git_buf *out, |
2a7d224f RB |
740 | git_filter_list *filters, |
741 | git_repository *repo, | |
742 | const char *path) | |
743 | { | |
5555696f | 744 | struct buf_stream writer; |
2a7d224f | 745 | int error; |
2a7d224f | 746 | |
5555696f | 747 | buf_stream_init(&writer, out); |
2a7d224f | 748 | |
5555696f | 749 | if ((error = git_filter_list_stream_file( |
69f0032b | 750 | filters, repo, path, &writer.parent)) < 0) |
5555696f | 751 | return error; |
2a7d224f | 752 | |
5555696f | 753 | assert(writer.complete); |
2a7d224f RB |
754 | return error; |
755 | } | |
756 | ||
fbdc9db3 ET |
757 | static int buf_from_blob(git_buf *out, git_blob *blob) |
758 | { | |
759 | git_off_t rawsize = git_blob_rawsize(blob); | |
760 | ||
761 | if (!git__is_sizet(rawsize)) { | |
ac3d33df | 762 | git_error_set(GIT_ERROR_OS, "blob is too large to filter"); |
fbdc9db3 ET |
763 | return -1; |
764 | } | |
765 | ||
d4cf1675 | 766 | git_buf_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); |
fbdc9db3 ET |
767 | return 0; |
768 | } | |
769 | ||
2a7d224f | 770 | int git_filter_list_apply_to_blob( |
a9f51e43 | 771 | git_buf *out, |
2a7d224f RB |
772 | git_filter_list *filters, |
773 | git_blob *blob) | |
774 | { | |
5555696f ET |
775 | struct buf_stream writer; |
776 | int error; | |
71379313 | 777 | |
5555696f | 778 | buf_stream_init(&writer, out); |
fbdc9db3 | 779 | |
5555696f | 780 | if ((error = git_filter_list_stream_blob( |
69f0032b | 781 | filters, blob, &writer.parent)) < 0) |
5555696f | 782 | return error; |
fbdc9db3 | 783 | |
5555696f ET |
784 | assert(writer.complete); |
785 | return error; | |
fbdc9db3 ET |
786 | } |
787 | ||
788 | struct proxy_stream { | |
f7c0125f | 789 | git_writestream parent; |
fbdc9db3 ET |
790 | git_filter *filter; |
791 | const git_filter_source *source; | |
792 | void **payload; | |
793 | git_buf input; | |
646364e7 ET |
794 | git_buf temp_buf; |
795 | git_buf *output; | |
b75f15aa | 796 | git_writestream *target; |
fbdc9db3 ET |
797 | }; |
798 | ||
799 | static int proxy_stream_write( | |
b75f15aa | 800 | git_writestream *s, const char *buffer, size_t len) |
fbdc9db3 ET |
801 | { |
802 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
803 | assert(proxy_stream); | |
804 | ||
805 | return git_buf_put(&proxy_stream->input, buffer, len); | |
806 | } | |
807 | ||
b75f15aa | 808 | static int proxy_stream_close(git_writestream *s) |
fbdc9db3 ET |
809 | { |
810 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
811 | git_buf *writebuf; | |
ac3d33df | 812 | git_error_state error_state = {0}; |
fbdc9db3 ET |
813 | int error; |
814 | ||
815 | assert(proxy_stream); | |
816 | ||
817 | error = proxy_stream->filter->apply( | |
818 | proxy_stream->filter, | |
819 | proxy_stream->payload, | |
646364e7 | 820 | proxy_stream->output, |
fbdc9db3 ET |
821 | &proxy_stream->input, |
822 | proxy_stream->source); | |
823 | ||
824 | if (error == GIT_PASSTHROUGH) { | |
825 | writebuf = &proxy_stream->input; | |
826 | } else if (error == 0) { | |
646364e7 ET |
827 | git_buf_sanitize(proxy_stream->output); |
828 | writebuf = proxy_stream->output; | |
fbdc9db3 | 829 | } else { |
ac3d33df JK |
830 | /* close stream before erroring out taking care |
831 | * to preserve the original error */ | |
832 | git_error_state_capture(&error_state, error); | |
833 | proxy_stream->target->close(proxy_stream->target); | |
834 | git_error_state_restore(&error_state); | |
fbdc9db3 ET |
835 | return error; |
836 | } | |
837 | ||
838 | if ((error = proxy_stream->target->write( | |
839 | proxy_stream->target, writebuf->ptr, writebuf->size)) == 0) | |
840 | error = proxy_stream->target->close(proxy_stream->target); | |
841 | ||
842 | return error; | |
843 | } | |
844 | ||
b75f15aa | 845 | static void proxy_stream_free(git_writestream *s) |
fbdc9db3 ET |
846 | { |
847 | struct proxy_stream *proxy_stream = (struct proxy_stream *)s; | |
848 | assert(proxy_stream); | |
849 | ||
ac3d33df JK |
850 | git_buf_dispose(&proxy_stream->input); |
851 | git_buf_dispose(&proxy_stream->temp_buf); | |
fbdc9db3 ET |
852 | git__free(proxy_stream); |
853 | } | |
854 | ||
855 | static int proxy_stream_init( | |
b75f15aa | 856 | git_writestream **out, |
fbdc9db3 | 857 | git_filter *filter, |
646364e7 | 858 | git_buf *temp_buf, |
fbdc9db3 ET |
859 | void **payload, |
860 | const git_filter_source *source, | |
b75f15aa | 861 | git_writestream *target) |
fbdc9db3 ET |
862 | { |
863 | struct proxy_stream *proxy_stream = git__calloc(1, sizeof(struct proxy_stream)); | |
ac3d33df | 864 | GIT_ERROR_CHECK_ALLOC(proxy_stream); |
fbdc9db3 | 865 | |
f7c0125f ET |
866 | proxy_stream->parent.write = proxy_stream_write; |
867 | proxy_stream->parent.close = proxy_stream_close; | |
868 | proxy_stream->parent.free = proxy_stream_free; | |
fbdc9db3 ET |
869 | proxy_stream->filter = filter; |
870 | proxy_stream->payload = payload; | |
871 | proxy_stream->source = source; | |
872 | proxy_stream->target = target; | |
646364e7 | 873 | proxy_stream->output = temp_buf ? temp_buf : &proxy_stream->temp_buf; |
fbdc9db3 | 874 | |
669ae274 ET |
875 | if (temp_buf) |
876 | git_buf_clear(temp_buf); | |
877 | ||
b75f15aa | 878 | *out = (git_writestream *)proxy_stream; |
fbdc9db3 ET |
879 | return 0; |
880 | } | |
881 | ||
882 | static int stream_list_init( | |
b75f15aa | 883 | git_writestream **out, |
fbdc9db3 ET |
884 | git_vector *streams, |
885 | git_filter_list *filters, | |
b75f15aa | 886 | git_writestream *target) |
fbdc9db3 | 887 | { |
b75f15aa | 888 | git_writestream *last_stream = target; |
fbdc9db3 ET |
889 | size_t i; |
890 | int error = 0; | |
891 | ||
892 | *out = NULL; | |
893 | ||
894 | if (!filters) { | |
895 | *out = target; | |
896 | return 0; | |
897 | } | |
898 | ||
899 | /* Create filters last to first to get the chaining direction */ | |
900 | for (i = 0; i < git_array_size(filters->filters); ++i) { | |
901 | size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? | |
902 | git_array_size(filters->filters) - 1 - i : i; | |
903 | git_filter_entry *fe = git_array_get(filters->filters, filter_idx); | |
b75f15aa | 904 | git_writestream *filter_stream; |
a78441bc | 905 | |
fbdc9db3 ET |
906 | assert(fe->filter->stream || fe->filter->apply); |
907 | ||
5555696f ET |
908 | /* If necessary, create a stream that proxies the traditional |
909 | * application. | |
910 | */ | |
646364e7 ET |
911 | if (fe->filter->stream) |
912 | error = fe->filter->stream(&filter_stream, fe->filter, | |
5555696f | 913 | &fe->payload, &filters->source, last_stream); |
646364e7 ET |
914 | else |
915 | /* Create a stream that proxies the one-shot apply */ | |
916 | error = proxy_stream_init(&filter_stream, fe->filter, | |
917 | filters->temp_buf, &fe->payload, &filters->source, | |
918 | last_stream); | |
fbdc9db3 | 919 | |
5555696f | 920 | if (error < 0) |
cf07db2f | 921 | goto out; |
5555696f ET |
922 | |
923 | git_vector_insert(streams, filter_stream); | |
fbdc9db3 | 924 | last_stream = filter_stream; |
71379313 RB |
925 | } |
926 | ||
cf07db2f PS |
927 | out: |
928 | if (error) | |
929 | last_stream->close(last_stream); | |
930 | else | |
931 | *out = last_stream; | |
932 | ||
933 | return error; | |
fbdc9db3 ET |
934 | } |
935 | ||
936 | void stream_list_free(git_vector *streams) | |
937 | { | |
b75f15aa | 938 | git_writestream *stream; |
fbdc9db3 ET |
939 | size_t i; |
940 | ||
941 | git_vector_foreach(streams, i, stream) | |
942 | stream->free(stream); | |
5555696f | 943 | git_vector_free(streams); |
fbdc9db3 ET |
944 | } |
945 | ||
fbdc9db3 ET |
946 | int git_filter_list_stream_file( |
947 | git_filter_list *filters, | |
fbdc9db3 ET |
948 | git_repository *repo, |
949 | const char *path, | |
b75f15aa | 950 | git_writestream *target) |
fbdc9db3 | 951 | { |
7dd22538 | 952 | char buf[FILTERIO_BUFSIZE]; |
5555696f | 953 | git_buf abspath = GIT_BUF_INIT; |
fbdc9db3 ET |
954 | const char *base = repo ? git_repository_workdir(repo) : NULL; |
955 | git_vector filter_streams = GIT_VECTOR_INIT; | |
b75f15aa | 956 | git_writestream *stream_start; |
fbdc9db3 | 957 | ssize_t readlen; |
cf07db2f | 958 | int fd = -1, error, initialized = 0; |
fbdc9db3 ET |
959 | |
960 | if ((error = stream_list_init( | |
961 | &stream_start, &filter_streams, filters, target)) < 0 || | |
962 | (error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0) | |
963 | goto done; | |
cf07db2f | 964 | initialized = 1; |
fbdc9db3 | 965 | |
6a2edc5a | 966 | if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { |
fbdc9db3 ET |
967 | error = fd; |
968 | goto done; | |
969 | } | |
970 | ||
7dd22538 | 971 | while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { |
5555696f | 972 | if ((error = stream_start->write(stream_start, buf, readlen)) < 0) |
fbdc9db3 ET |
973 | goto done; |
974 | } | |
975 | ||
cf07db2f | 976 | if (readlen < 0) |
ac3d33df | 977 | error = -1; |
fbdc9db3 | 978 | |
fbdc9db3 | 979 | done: |
cf07db2f PS |
980 | if (initialized) |
981 | error |= stream_start->close(stream_start); | |
982 | ||
0137aba5 CMN |
983 | if (fd >= 0) |
984 | p_close(fd); | |
fbdc9db3 | 985 | stream_list_free(&filter_streams); |
ac3d33df | 986 | git_buf_dispose(&abspath); |
fbdc9db3 ET |
987 | return error; |
988 | } | |
989 | ||
990 | int git_filter_list_stream_data( | |
991 | git_filter_list *filters, | |
992 | git_buf *data, | |
b75f15aa | 993 | git_writestream *target) |
fbdc9db3 ET |
994 | { |
995 | git_vector filter_streams = GIT_VECTOR_INIT; | |
b75f15aa | 996 | git_writestream *stream_start; |
cf07db2f | 997 | int error, initialized = 0; |
fbdc9db3 | 998 | |
5555696f ET |
999 | git_buf_sanitize(data); |
1000 | ||
4de7f3bf CMN |
1001 | if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) |
1002 | goto out; | |
cf07db2f | 1003 | initialized = 1; |
fbdc9db3 | 1004 | |
cf07db2f PS |
1005 | if ((error = stream_start->write( |
1006 | stream_start, data->ptr, data->size)) < 0) | |
1007 | goto out; | |
4de7f3bf CMN |
1008 | |
1009 | out: | |
cf07db2f PS |
1010 | if (initialized) |
1011 | error |= stream_start->close(stream_start); | |
1012 | ||
fbdc9db3 | 1013 | stream_list_free(&filter_streams); |
cf07db2f | 1014 | return error; |
fbdc9db3 ET |
1015 | } |
1016 | ||
1017 | int git_filter_list_stream_blob( | |
1018 | git_filter_list *filters, | |
1019 | git_blob *blob, | |
b75f15aa | 1020 | git_writestream *target) |
fbdc9db3 ET |
1021 | { |
1022 | git_buf in = GIT_BUF_INIT; | |
1023 | ||
1024 | if (buf_from_blob(&in, blob) < 0) | |
1025 | return -1; | |
2a7d224f | 1026 | |
4b11f25a RB |
1027 | if (filters) |
1028 | git_oid_cpy(&filters->source.oid, git_blob_id(blob)); | |
1029 | ||
fbdc9db3 | 1030 | return git_filter_list_stream_data(filters, &in, target); |
2a7d224f | 1031 | } |
a78441bc MM |
1032 | |
1033 | int git_filter_init(git_filter *filter, unsigned int version) | |
1034 | { | |
1035 | GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); | |
1036 | return 0; | |
1037 | } |