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