]> git.proxmox.com Git - libgit2.git/blame - src/filter.c
install as examples
[libgit2.git] / src / filter.c
CommitLineData
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
21struct 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 30typedef struct {
2eecc288 31 const char *filter_name;
85d54812
RB
32 git_filter *filter;
33 void *payload;
34} git_filter_entry;
35
36struct 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
43typedef 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
53static 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
60struct git_filter_registry {
61 git_rwlock lock;
0646634e 62 git_vector filters;
974774c7
RB
63};
64
2ed855a9 65static struct git_filter_registry filter_registry;
0646634e 66
2ed855a9 67static void git_filter_global_shutdown(void);
4b11f25a 68
0646634e 69
974774c7
RB
70static 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
104static 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
136static 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
143static 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 */
150static 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
189int 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
211done:
212 if (error) {
213 git_filter_free(crlf);
214 git_filter_free(ident);
215 }
216
217 return error;
218}
219
220static 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 */
246static 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 */
253static 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
264int 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
285done:
286 git_rwlock_wrunlock(&filter_registry.lock);
287 return error;
288}
289
974774c7
RB
290int 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
326done:
327 git_rwlock_wrunlock(&filter_registry.lock);
328 return error;
974774c7
RB
329}
330
0646634e
RB
331static 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
344git_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
361done:
362 git_rwlock_rdunlock(&filter_registry.lock);
363 return filter;
974774c7
RB
364}
365
4b11f25a
RB
366void git_filter_free(git_filter *filter)
367{
368 git__free(filter);
369}
370
570ba25c
RB
371git_repository *git_filter_source_repo(const git_filter_source *src)
372{
373 return src->repo;
374}
375
376const char *git_filter_source_path(const git_filter_source *src)
377{
378 return src->path;
379}
380
381uint16_t git_filter_source_filemode(const git_filter_source *src)
382{
383 return src->filemode;
384}
385
386const 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
391git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
392{
393 return src->mode;
394}
395
795eaccd 396uint32_t git_filter_source_flags(const git_filter_source *src)
5269008c 397{
795eaccd 398 return src->flags;
5269008c
RB
399}
400
40cb40fa 401static 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 424static 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 480int 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 494int 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
582int 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
598void 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
615int 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
634int 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
672size_t git_filter_list_length(const git_filter_list *fl)
673{
674 return fl ? git_array_size(fl->filters) : 0;
675}
676
5555696f 677struct buf_stream {
f7c0125f 678 git_writestream parent;
5555696f
ET
679 git_buf *target;
680 bool complete;
681};
682
683static 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 694static 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 705static void buf_stream_free(git_writestream *s)
5555696f
ET
706{
707 GIT_UNUSED(s);
708}
85d54812 709
5555696f
ET
710static 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
722int 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
746int 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
765static 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 778int 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
796struct 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
807static 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 816static 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 853static 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
863static 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
890static 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
935out:
936 if (error)
937 last_stream->close(last_stream);
938 else
939 *out = last_stream;
940
941 return error;
fbdc9db3
ET
942}
943
22a2d3d5 944static 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
954int 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 987done:
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
998int 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
1017out:
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
1025int 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
1041int 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}