]> git.proxmox.com Git - libgit2.git/blob - src/libgit2/filter.c
New upstream version 1.5.0+ds
[libgit2.git] / src / libgit2 / filter.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
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 "filter.h"
9
10 #include "buf.h"
11 #include "common.h"
12 #include "futils.h"
13 #include "hash.h"
14 #include "repository.h"
15 #include "runtime.h"
16 #include "git2/sys/filter.h"
17 #include "git2/config.h"
18 #include "blob.h"
19 #include "attr_file.h"
20 #include "array.h"
21 #include "path.h"
22
23 struct git_filter_source {
24 git_repository *repo;
25 const char *path;
26 git_oid oid; /* zero if unknown (which is likely) */
27 uint16_t filemode; /* zero if unknown */
28 git_filter_mode_t mode;
29 git_filter_options options;
30 };
31
32 typedef struct {
33 const char *filter_name;
34 git_filter *filter;
35 void *payload;
36 } git_filter_entry;
37
38 struct git_filter_list {
39 git_array_t(git_filter_entry) filters;
40 git_filter_source source;
41 git_str *temp_buf;
42 char path[GIT_FLEX_ARRAY];
43 };
44
45 typedef struct {
46 char *filter_name;
47 git_filter *filter;
48 int priority;
49 int initialized;
50 size_t nattrs, nmatches;
51 char *attrdata;
52 const char *attrs[GIT_FLEX_ARRAY];
53 } git_filter_def;
54
55 static int filter_def_priority_cmp(const void *a, const void *b)
56 {
57 int pa = ((const git_filter_def *)a)->priority;
58 int pb = ((const git_filter_def *)b)->priority;
59 return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
60 }
61
62 struct git_filter_registry {
63 git_rwlock lock;
64 git_vector filters;
65 };
66
67 static struct git_filter_registry filter_registry;
68
69 static void git_filter_global_shutdown(void);
70
71
72 static int filter_def_scan_attrs(
73 git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
74 {
75 const char *start, *scan = attr_str;
76 int has_eq;
77
78 *nattr = *nmatch = 0;
79
80 if (!scan)
81 return 0;
82
83 while (*scan) {
84 while (git__isspace(*scan)) scan++;
85
86 for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
87 if (*scan == '=')
88 has_eq = 1;
89 }
90
91 if (scan > start) {
92 (*nattr)++;
93 if (has_eq || *start == '-' || *start == '+' || *start == '!')
94 (*nmatch)++;
95
96 if (has_eq)
97 git_str_putc(attrs, '=');
98 git_str_put(attrs, start, scan - start);
99 git_str_putc(attrs, '\0');
100 }
101 }
102
103 return 0;
104 }
105
106 static void filter_def_set_attrs(git_filter_def *fdef)
107 {
108 char *scan = fdef->attrdata;
109 size_t i;
110
111 for (i = 0; i < fdef->nattrs; ++i) {
112 const char *name, *value;
113
114 switch (*scan) {
115 case '=':
116 name = scan + 1;
117 for (scan++; *scan != '='; scan++) /* find '=' */;
118 *scan++ = '\0';
119 value = scan;
120 break;
121 case '-':
122 name = scan + 1; value = git_attr__false; break;
123 case '+':
124 name = scan + 1; value = git_attr__true; break;
125 case '!':
126 name = scan + 1; value = git_attr__unset; break;
127 default:
128 name = scan; value = NULL; break;
129 }
130
131 fdef->attrs[i] = name;
132 fdef->attrs[i + fdef->nattrs] = value;
133
134 scan += strlen(scan) + 1;
135 }
136 }
137
138 static int filter_def_name_key_check(const void *key, const void *fdef)
139 {
140 const char *name =
141 fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
142 return name ? git__strcmp(key, name) : -1;
143 }
144
145 static int filter_def_filter_key_check(const void *key, const void *fdef)
146 {
147 const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
148 return (key == filter) ? 0 : -1;
149 }
150
151 /* Note: callers must lock the registry before calling this function */
152 static int filter_registry_insert(
153 const char *name, git_filter *filter, int priority)
154 {
155 git_filter_def *fdef;
156 size_t nattr = 0, nmatch = 0, alloc_len;
157 git_str attrs = GIT_STR_INIT;
158
159 if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
160 return -1;
161
162 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2);
163 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *));
164 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def));
165
166 fdef = git__calloc(1, alloc_len);
167 GIT_ERROR_CHECK_ALLOC(fdef);
168
169 fdef->filter_name = git__strdup(name);
170 GIT_ERROR_CHECK_ALLOC(fdef->filter_name);
171
172 fdef->filter = filter;
173 fdef->priority = priority;
174 fdef->nattrs = nattr;
175 fdef->nmatches = nmatch;
176 fdef->attrdata = git_str_detach(&attrs);
177
178 filter_def_set_attrs(fdef);
179
180 if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
181 git__free(fdef->filter_name);
182 git__free(fdef->attrdata);
183 git__free(fdef);
184 return -1;
185 }
186
187 git_vector_sort(&filter_registry.filters);
188 return 0;
189 }
190
191 int git_filter_global_init(void)
192 {
193 git_filter *crlf = NULL, *ident = NULL;
194 int error = 0;
195
196 if (git_rwlock_init(&filter_registry.lock) < 0)
197 return -1;
198
199 if ((error = git_vector_init(&filter_registry.filters, 2,
200 filter_def_priority_cmp)) < 0)
201 goto done;
202
203 if ((crlf = git_crlf_filter_new()) == NULL ||
204 filter_registry_insert(
205 GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 ||
206 (ident = git_ident_filter_new()) == NULL ||
207 filter_registry_insert(
208 GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
209 error = -1;
210
211 if (!error)
212 error = git_runtime_shutdown_register(git_filter_global_shutdown);
213
214 done:
215 if (error) {
216 git_filter_free(crlf);
217 git_filter_free(ident);
218 }
219
220 return error;
221 }
222
223 static void git_filter_global_shutdown(void)
224 {
225 size_t pos;
226 git_filter_def *fdef;
227
228 if (git_rwlock_wrlock(&filter_registry.lock) < 0)
229 return;
230
231 git_vector_foreach(&filter_registry.filters, pos, fdef) {
232 if (fdef->filter && fdef->filter->shutdown) {
233 fdef->filter->shutdown(fdef->filter);
234 fdef->initialized = false;
235 }
236
237 git__free(fdef->filter_name);
238 git__free(fdef->attrdata);
239 git__free(fdef);
240 }
241
242 git_vector_free(&filter_registry.filters);
243
244 git_rwlock_wrunlock(&filter_registry.lock);
245 git_rwlock_free(&filter_registry.lock);
246 }
247
248 /* Note: callers must lock the registry before calling this function */
249 static int filter_registry_find(size_t *pos, const char *name)
250 {
251 return git_vector_search2(
252 pos, &filter_registry.filters, filter_def_name_key_check, name);
253 }
254
255 /* Note: callers must lock the registry before calling this function */
256 static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
257 {
258 git_filter_def *fdef = NULL;
259
260 if (!filter_registry_find(pos, name))
261 fdef = git_vector_get(&filter_registry.filters, *pos);
262
263 return fdef;
264 }
265
266
267 int git_filter_register(
268 const char *name, git_filter *filter, int priority)
269 {
270 int error;
271
272 GIT_ASSERT_ARG(name);
273 GIT_ASSERT_ARG(filter);
274
275 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
276 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
277 return -1;
278 }
279
280 if (!filter_registry_find(NULL, name)) {
281 git_error_set(
282 GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name);
283 error = GIT_EEXISTS;
284 goto done;
285 }
286
287 error = filter_registry_insert(name, filter, priority);
288
289 done:
290 git_rwlock_wrunlock(&filter_registry.lock);
291 return error;
292 }
293
294 int git_filter_unregister(const char *name)
295 {
296 size_t pos;
297 git_filter_def *fdef;
298 int error = 0;
299
300 GIT_ASSERT_ARG(name);
301
302 /* cannot unregister default filters */
303 if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
304 git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name);
305 return -1;
306 }
307
308 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
309 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
310 return -1;
311 }
312
313 if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
314 git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name);
315 error = GIT_ENOTFOUND;
316 goto done;
317 }
318
319 git_vector_remove(&filter_registry.filters, pos);
320
321 if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
322 fdef->filter->shutdown(fdef->filter);
323 fdef->initialized = false;
324 }
325
326 git__free(fdef->filter_name);
327 git__free(fdef->attrdata);
328 git__free(fdef);
329
330 done:
331 git_rwlock_wrunlock(&filter_registry.lock);
332 return error;
333 }
334
335 static int filter_initialize(git_filter_def *fdef)
336 {
337 int error = 0;
338
339 if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
340 if ((error = fdef->filter->initialize(fdef->filter)) < 0)
341 return error;
342 }
343
344 fdef->initialized = true;
345 return 0;
346 }
347
348 git_filter *git_filter_lookup(const char *name)
349 {
350 size_t pos;
351 git_filter_def *fdef;
352 git_filter *filter = NULL;
353
354 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
355 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
356 return NULL;
357 }
358
359 if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
360 (!fdef->initialized && filter_initialize(fdef) < 0))
361 goto done;
362
363 filter = fdef->filter;
364
365 done:
366 git_rwlock_rdunlock(&filter_registry.lock);
367 return filter;
368 }
369
370 void git_filter_free(git_filter *filter)
371 {
372 git__free(filter);
373 }
374
375 git_repository *git_filter_source_repo(const git_filter_source *src)
376 {
377 return src->repo;
378 }
379
380 const char *git_filter_source_path(const git_filter_source *src)
381 {
382 return src->path;
383 }
384
385 uint16_t git_filter_source_filemode(const git_filter_source *src)
386 {
387 return src->filemode;
388 }
389
390 const git_oid *git_filter_source_id(const git_filter_source *src)
391 {
392 return git_oid_is_zero(&src->oid) ? NULL : &src->oid;
393 }
394
395 git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
396 {
397 return src->mode;
398 }
399
400 uint32_t git_filter_source_flags(const git_filter_source *src)
401 {
402 return src->options.flags;
403 }
404
405 static int filter_list_new(
406 git_filter_list **out, const git_filter_source *src)
407 {
408 git_filter_list *fl = NULL;
409 size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
410
411 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
412 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
413
414 fl = git__calloc(1, alloclen);
415 GIT_ERROR_CHECK_ALLOC(fl);
416
417 if (src->path)
418 memcpy(fl->path, src->path, pathlen);
419 fl->source.repo = src->repo;
420 fl->source.path = fl->path;
421 fl->source.mode = src->mode;
422
423 memcpy(&fl->source.options, &src->options, sizeof(git_filter_options));
424
425 *out = fl;
426 return 0;
427 }
428
429 static int filter_list_check_attributes(
430 const char ***out,
431 git_repository *repo,
432 git_filter_session *filter_session,
433 git_filter_def *fdef,
434 const git_filter_source *src)
435 {
436 const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
437 git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT;
438 size_t i;
439 int error;
440
441 GIT_ERROR_CHECK_ALLOC(strs);
442
443 if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
444 attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM;
445
446 if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0)
447 attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD;
448
449 if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) {
450 attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT;
451
452 #ifndef GIT_DEPRECATE_HARD
453 if (src->options.commit_id)
454 git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id);
455 else
456 #endif
457 git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id);
458 }
459
460 error = git_attr_get_many_with_session(
461 strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs);
462
463 /* if no values were found but no matches are needed, it's okay! */
464 if (error == GIT_ENOTFOUND && !fdef->nmatches) {
465 git_error_clear();
466 git__free((void *)strs);
467 return 0;
468 }
469
470 for (i = 0; !error && i < fdef->nattrs; ++i) {
471 const char *want = fdef->attrs[fdef->nattrs + i];
472 git_attr_value_t want_type, found_type;
473
474 if (!want)
475 continue;
476
477 want_type = git_attr_value(want);
478 found_type = git_attr_value(strs[i]);
479
480 if (want_type != found_type)
481 error = GIT_ENOTFOUND;
482 else if (want_type == GIT_ATTR_VALUE_STRING &&
483 strcmp(want, strs[i]) &&
484 strcmp(want, "*"))
485 error = GIT_ENOTFOUND;
486 }
487
488 if (error)
489 git__free((void *)strs);
490 else
491 *out = strs;
492
493 return error;
494 }
495
496 int git_filter_list_new(
497 git_filter_list **out,
498 git_repository *repo,
499 git_filter_mode_t mode,
500 uint32_t flags)
501 {
502 git_filter_source src = { 0 };
503 src.repo = repo;
504 src.path = NULL;
505 src.mode = mode;
506 src.options.flags = flags;
507 return filter_list_new(out, &src);
508 }
509
510 int git_filter_list__load(
511 git_filter_list **filters,
512 git_repository *repo,
513 git_blob *blob, /* can be NULL */
514 const char *path,
515 git_filter_mode_t mode,
516 git_filter_session *filter_session)
517 {
518 int error = 0;
519 git_filter_list *fl = NULL;
520 git_filter_source src = { 0 };
521 git_filter_entry *fe;
522 size_t idx;
523 git_filter_def *fdef;
524
525 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
526 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
527 return -1;
528 }
529
530 src.repo = repo;
531 src.path = path;
532 src.mode = mode;
533
534 memcpy(&src.options, &filter_session->options, sizeof(git_filter_options));
535
536 if (blob)
537 git_oid_cpy(&src.oid, git_blob_id(blob));
538
539 git_vector_foreach(&filter_registry.filters, idx, fdef) {
540 const char **values = NULL;
541 void *payload = NULL;
542
543 if (!fdef || !fdef->filter)
544 continue;
545
546 if (fdef->nattrs > 0) {
547 error = filter_list_check_attributes(
548 &values, repo,
549 filter_session, fdef, &src);
550
551 if (error == GIT_ENOTFOUND) {
552 error = 0;
553 continue;
554 } else if (error < 0)
555 break;
556 }
557
558 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
559 break;
560
561 if (fdef->filter->check)
562 error = fdef->filter->check(
563 fdef->filter, &payload, &src, values);
564
565 git__free((void *)values);
566
567 if (error == GIT_PASSTHROUGH)
568 error = 0;
569 else if (error < 0)
570 break;
571 else {
572 if (!fl) {
573 if ((error = filter_list_new(&fl, &src)) < 0)
574 break;
575
576 fl->temp_buf = filter_session->temp_buf;
577 }
578
579 fe = git_array_alloc(fl->filters);
580 GIT_ERROR_CHECK_ALLOC(fe);
581
582 fe->filter = fdef->filter;
583 fe->filter_name = fdef->filter_name;
584 fe->payload = payload;
585 }
586 }
587
588 git_rwlock_rdunlock(&filter_registry.lock);
589
590 if (error && fl != NULL) {
591 git_array_clear(fl->filters);
592 git__free(fl);
593 fl = NULL;
594 }
595
596 *filters = fl;
597 return error;
598 }
599
600 int git_filter_list_load_ext(
601 git_filter_list **filters,
602 git_repository *repo,
603 git_blob *blob, /* can be NULL */
604 const char *path,
605 git_filter_mode_t mode,
606 git_filter_options *opts)
607 {
608 git_filter_session filter_session = GIT_FILTER_SESSION_INIT;
609
610 if (opts)
611 memcpy(&filter_session.options, opts, sizeof(git_filter_options));
612
613 return git_filter_list__load(
614 filters, repo, blob, path, mode, &filter_session);
615 }
616
617 int git_filter_list_load(
618 git_filter_list **filters,
619 git_repository *repo,
620 git_blob *blob, /* can be NULL */
621 const char *path,
622 git_filter_mode_t mode,
623 uint32_t flags)
624 {
625 git_filter_session filter_session = GIT_FILTER_SESSION_INIT;
626
627 filter_session.options.flags = flags;
628
629 return git_filter_list__load(
630 filters, repo, blob, path, mode, &filter_session);
631 }
632
633 void git_filter_list_free(git_filter_list *fl)
634 {
635 uint32_t i;
636
637 if (!fl)
638 return;
639
640 for (i = 0; i < git_array_size(fl->filters); ++i) {
641 git_filter_entry *fe = git_array_get(fl->filters, i);
642 if (fe->filter->cleanup)
643 fe->filter->cleanup(fe->filter, fe->payload);
644 }
645
646 git_array_clear(fl->filters);
647 git__free(fl);
648 }
649
650 int git_filter_list_contains(
651 git_filter_list *fl,
652 const char *name)
653 {
654 size_t i;
655
656 GIT_ASSERT_ARG(name);
657
658 if (!fl)
659 return 0;
660
661 for (i = 0; i < fl->filters.size; i++) {
662 if (strcmp(fl->filters.ptr[i].filter_name, name) == 0)
663 return 1;
664 }
665
666 return 0;
667 }
668
669 int git_filter_list_push(
670 git_filter_list *fl, git_filter *filter, void *payload)
671 {
672 int error = 0;
673 size_t pos;
674 git_filter_def *fdef = NULL;
675 git_filter_entry *fe;
676
677 GIT_ASSERT_ARG(fl);
678 GIT_ASSERT_ARG(filter);
679
680 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
681 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
682 return -1;
683 }
684
685 if (git_vector_search2(
686 &pos, &filter_registry.filters,
687 filter_def_filter_key_check, filter) == 0)
688 fdef = git_vector_get(&filter_registry.filters, pos);
689
690 git_rwlock_rdunlock(&filter_registry.lock);
691
692 if (fdef == NULL) {
693 git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter");
694 return -1;
695 }
696
697 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
698 return error;
699
700 fe = git_array_alloc(fl->filters);
701 GIT_ERROR_CHECK_ALLOC(fe);
702 fe->filter = filter;
703 fe->payload = payload;
704
705 return 0;
706 }
707
708 size_t git_filter_list_length(const git_filter_list *fl)
709 {
710 return fl ? git_array_size(fl->filters) : 0;
711 }
712
713 struct buf_stream {
714 git_writestream parent;
715 git_str *target;
716 bool complete;
717 };
718
719 static int buf_stream_write(
720 git_writestream *s, const char *buffer, size_t len)
721 {
722 struct buf_stream *buf_stream = (struct buf_stream *)s;
723 GIT_ASSERT_ARG(buf_stream);
724 GIT_ASSERT(buf_stream->complete == 0);
725
726 return git_str_put(buf_stream->target, buffer, len);
727 }
728
729 static int buf_stream_close(git_writestream *s)
730 {
731 struct buf_stream *buf_stream = (struct buf_stream *)s;
732 GIT_ASSERT_ARG(buf_stream);
733
734 GIT_ASSERT(buf_stream->complete == 0);
735 buf_stream->complete = 1;
736
737 return 0;
738 }
739
740 static void buf_stream_free(git_writestream *s)
741 {
742 GIT_UNUSED(s);
743 }
744
745 static void buf_stream_init(struct buf_stream *writer, git_str *target)
746 {
747 memset(writer, 0, sizeof(struct buf_stream));
748
749 writer->parent.write = buf_stream_write;
750 writer->parent.close = buf_stream_close;
751 writer->parent.free = buf_stream_free;
752 writer->target = target;
753
754 git_str_clear(target);
755 }
756
757 int git_filter_list_apply_to_buffer(
758 git_buf *out,
759 git_filter_list *filters,
760 const char *in,
761 size_t in_len)
762 {
763 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len);
764 }
765
766 int git_filter_list__apply_to_buffer(
767 git_str *out,
768 git_filter_list *filters,
769 const char *in,
770 size_t in_len)
771 {
772 struct buf_stream writer;
773 int error;
774
775 buf_stream_init(&writer, out);
776
777 if ((error = git_filter_list_stream_buffer(filters,
778 in, in_len, &writer.parent)) < 0)
779 return error;
780
781 GIT_ASSERT(writer.complete);
782 return error;
783 }
784
785 int git_filter_list__convert_buf(
786 git_str *out,
787 git_filter_list *filters,
788 git_str *in)
789 {
790 int error;
791
792 if (!filters || git_filter_list_length(filters) == 0) {
793 git_str_swap(out, in);
794 git_str_dispose(in);
795 return 0;
796 }
797
798 error = git_filter_list__apply_to_buffer(out, filters,
799 in->ptr, in->size);
800
801 if (!error)
802 git_str_dispose(in);
803
804 return error;
805 }
806
807 int git_filter_list_apply_to_file(
808 git_buf *out,
809 git_filter_list *filters,
810 git_repository *repo,
811 const char *path)
812 {
813 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path);
814 }
815
816 int git_filter_list__apply_to_file(
817 git_str *out,
818 git_filter_list *filters,
819 git_repository *repo,
820 const char *path)
821 {
822 struct buf_stream writer;
823 int error;
824
825 buf_stream_init(&writer, out);
826
827 if ((error = git_filter_list_stream_file(
828 filters, repo, path, &writer.parent)) < 0)
829 return error;
830
831 GIT_ASSERT(writer.complete);
832 return error;
833 }
834
835 static int buf_from_blob(git_str *out, git_blob *blob)
836 {
837 git_object_size_t rawsize = git_blob_rawsize(blob);
838
839 if (!git__is_sizet(rawsize)) {
840 git_error_set(GIT_ERROR_OS, "blob is too large to filter");
841 return -1;
842 }
843
844 git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
845 return 0;
846 }
847
848 int git_filter_list_apply_to_blob(
849 git_buf *out,
850 git_filter_list *filters,
851 git_blob *blob)
852 {
853 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob);
854 }
855
856 int git_filter_list__apply_to_blob(
857 git_str *out,
858 git_filter_list *filters,
859 git_blob *blob)
860 {
861 struct buf_stream writer;
862 int error;
863
864 buf_stream_init(&writer, out);
865
866 if ((error = git_filter_list_stream_blob(
867 filters, blob, &writer.parent)) < 0)
868 return error;
869
870 GIT_ASSERT(writer.complete);
871 return error;
872 }
873
874 struct buffered_stream {
875 git_writestream parent;
876 git_filter *filter;
877 int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *);
878 int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *);
879 const git_filter_source *source;
880 void **payload;
881 git_str input;
882 git_str temp_buf;
883 git_str *output;
884 git_writestream *target;
885 };
886
887 static int buffered_stream_write(
888 git_writestream *s, const char *buffer, size_t len)
889 {
890 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
891 GIT_ASSERT_ARG(buffered_stream);
892
893 return git_str_put(&buffered_stream->input, buffer, len);
894 }
895
896 #ifndef GIT_DEPRECATE_HARD
897 # define BUF_TO_STRUCT(b, s) \
898 (b)->ptr = (s)->ptr; \
899 (b)->size = (s)->size; \
900 (b)->reserved = (s)->asize;
901 # define STRUCT_TO_BUF(s, b) \
902 (s)->ptr = (b)->ptr; \
903 (s)->size = (b)->size; \
904 (s)->asize = (b)->reserved;
905 #endif
906
907 static int buffered_stream_close(git_writestream *s)
908 {
909 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
910 git_str *writebuf;
911 git_error_state error_state = {0};
912 int error;
913
914 GIT_ASSERT_ARG(buffered_stream);
915
916 #ifndef GIT_DEPRECATE_HARD
917 if (buffered_stream->write_fn == NULL) {
918 git_buf legacy_output = GIT_BUF_INIT,
919 legacy_input = GIT_BUF_INIT;
920
921 BUF_TO_STRUCT(&legacy_output, buffered_stream->output);
922 BUF_TO_STRUCT(&legacy_input, &buffered_stream->input);
923
924 error = buffered_stream->legacy_write_fn(
925 buffered_stream->filter,
926 buffered_stream->payload,
927 &legacy_output,
928 &legacy_input,
929 buffered_stream->source);
930
931 STRUCT_TO_BUF(buffered_stream->output, &legacy_output);
932 STRUCT_TO_BUF(&buffered_stream->input, &legacy_input);
933 } else
934 #endif
935 error = buffered_stream->write_fn(
936 buffered_stream->filter,
937 buffered_stream->payload,
938 buffered_stream->output,
939 &buffered_stream->input,
940 buffered_stream->source);
941
942 if (error == GIT_PASSTHROUGH) {
943 writebuf = &buffered_stream->input;
944 } else if (error == 0) {
945 writebuf = buffered_stream->output;
946 } else {
947 /* close stream before erroring out taking care
948 * to preserve the original error */
949 git_error_state_capture(&error_state, error);
950 buffered_stream->target->close(buffered_stream->target);
951 git_error_state_restore(&error_state);
952 return error;
953 }
954
955 if ((error = buffered_stream->target->write(
956 buffered_stream->target, writebuf->ptr, writebuf->size)) == 0)
957 error = buffered_stream->target->close(buffered_stream->target);
958
959 return error;
960 }
961
962 static void buffered_stream_free(git_writestream *s)
963 {
964 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
965
966 if (buffered_stream) {
967 git_str_dispose(&buffered_stream->input);
968 git_str_dispose(&buffered_stream->temp_buf);
969 git__free(buffered_stream);
970 }
971 }
972
973 int git_filter_buffered_stream_new(
974 git_writestream **out,
975 git_filter *filter,
976 int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *),
977 git_str *temp_buf,
978 void **payload,
979 const git_filter_source *source,
980 git_writestream *target)
981 {
982 struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream));
983 GIT_ERROR_CHECK_ALLOC(buffered_stream);
984
985 buffered_stream->parent.write = buffered_stream_write;
986 buffered_stream->parent.close = buffered_stream_close;
987 buffered_stream->parent.free = buffered_stream_free;
988 buffered_stream->filter = filter;
989 buffered_stream->write_fn = write_fn;
990 buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf;
991 buffered_stream->payload = payload;
992 buffered_stream->source = source;
993 buffered_stream->target = target;
994
995 if (temp_buf)
996 git_str_clear(temp_buf);
997
998 *out = (git_writestream *)buffered_stream;
999 return 0;
1000 }
1001
1002 #ifndef GIT_DEPRECATE_HARD
1003 static int buffered_legacy_stream_new(
1004 git_writestream **out,
1005 git_filter *filter,
1006 int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *),
1007 git_str *temp_buf,
1008 void **payload,
1009 const git_filter_source *source,
1010 git_writestream *target)
1011 {
1012 struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream));
1013 GIT_ERROR_CHECK_ALLOC(buffered_stream);
1014
1015 buffered_stream->parent.write = buffered_stream_write;
1016 buffered_stream->parent.close = buffered_stream_close;
1017 buffered_stream->parent.free = buffered_stream_free;
1018 buffered_stream->filter = filter;
1019 buffered_stream->legacy_write_fn = legacy_write_fn;
1020 buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf;
1021 buffered_stream->payload = payload;
1022 buffered_stream->source = source;
1023 buffered_stream->target = target;
1024
1025 if (temp_buf)
1026 git_str_clear(temp_buf);
1027
1028 *out = (git_writestream *)buffered_stream;
1029 return 0;
1030 }
1031 #endif
1032
1033 static int setup_stream(
1034 git_writestream **out,
1035 git_filter_entry *fe,
1036 git_filter_list *filters,
1037 git_writestream *last_stream)
1038 {
1039 #ifndef GIT_DEPRECATE_HARD
1040 GIT_ASSERT(fe->filter->stream || fe->filter->apply);
1041
1042 /*
1043 * If necessary, create a stream that proxies the traditional
1044 * application.
1045 */
1046 if (!fe->filter->stream) {
1047 /* Create a stream that proxies the one-shot apply */
1048 return buffered_legacy_stream_new(out,
1049 fe->filter, fe->filter->apply, filters->temp_buf,
1050 &fe->payload, &filters->source, last_stream);
1051 }
1052 #endif
1053
1054 GIT_ASSERT(fe->filter->stream);
1055 return fe->filter->stream(out, fe->filter,
1056 &fe->payload, &filters->source, last_stream);
1057 }
1058
1059 static int stream_list_init(
1060 git_writestream **out,
1061 git_vector *streams,
1062 git_filter_list *filters,
1063 git_writestream *target)
1064 {
1065 git_writestream *last_stream = target;
1066 size_t i;
1067 int error = 0;
1068
1069 *out = NULL;
1070
1071 if (!filters) {
1072 *out = target;
1073 return 0;
1074 }
1075
1076 /* Create filters last to first to get the chaining direction */
1077 for (i = 0; i < git_array_size(filters->filters); ++i) {
1078 size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ?
1079 git_array_size(filters->filters) - 1 - i : i;
1080
1081 git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
1082 git_writestream *filter_stream;
1083
1084 error = setup_stream(&filter_stream, fe, filters, last_stream);
1085
1086 if (error < 0)
1087 goto out;
1088
1089 git_vector_insert(streams, filter_stream);
1090 last_stream = filter_stream;
1091 }
1092
1093 out:
1094 if (error)
1095 last_stream->close(last_stream);
1096 else
1097 *out = last_stream;
1098
1099 return error;
1100 }
1101
1102 static void filter_streams_free(git_vector *streams)
1103 {
1104 git_writestream *stream;
1105 size_t i;
1106
1107 git_vector_foreach(streams, i, stream)
1108 stream->free(stream);
1109 git_vector_free(streams);
1110 }
1111
1112 int git_filter_list_stream_file(
1113 git_filter_list *filters,
1114 git_repository *repo,
1115 const char *path,
1116 git_writestream *target)
1117 {
1118 char buf[GIT_BUFSIZE_FILTERIO];
1119 git_str abspath = GIT_STR_INIT;
1120 const char *base = repo ? git_repository_workdir(repo) : NULL;
1121 git_vector filter_streams = GIT_VECTOR_INIT;
1122 git_writestream *stream_start;
1123 ssize_t readlen;
1124 int fd = -1, error, initialized = 0;
1125
1126 if ((error = stream_list_init(
1127 &stream_start, &filter_streams, filters, target)) < 0 ||
1128 (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 ||
1129 (error = git_path_validate_str_length(repo, &abspath)) < 0)
1130 goto done;
1131
1132 initialized = 1;
1133
1134 if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
1135 error = fd;
1136 goto done;
1137 }
1138
1139 while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
1140 if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
1141 goto done;
1142 }
1143
1144 if (readlen < 0)
1145 error = -1;
1146
1147 done:
1148 if (initialized)
1149 error |= stream_start->close(stream_start);
1150
1151 if (fd >= 0)
1152 p_close(fd);
1153 filter_streams_free(&filter_streams);
1154 git_str_dispose(&abspath);
1155 return error;
1156 }
1157
1158 int git_filter_list_stream_buffer(
1159 git_filter_list *filters,
1160 const char *buffer,
1161 size_t len,
1162 git_writestream *target)
1163 {
1164 git_vector filter_streams = GIT_VECTOR_INIT;
1165 git_writestream *stream_start;
1166 int error, initialized = 0;
1167
1168 if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
1169 goto out;
1170 initialized = 1;
1171
1172 if ((error = stream_start->write(stream_start, buffer, len)) < 0)
1173 goto out;
1174
1175 out:
1176 if (initialized)
1177 error |= stream_start->close(stream_start);
1178
1179 filter_streams_free(&filter_streams);
1180 return error;
1181 }
1182
1183 int git_filter_list_stream_blob(
1184 git_filter_list *filters,
1185 git_blob *blob,
1186 git_writestream *target)
1187 {
1188 git_str in = GIT_STR_INIT;
1189
1190 if (buf_from_blob(&in, blob) < 0)
1191 return -1;
1192
1193 if (filters)
1194 git_oid_cpy(&filters->source.oid, git_blob_id(blob));
1195
1196 return git_filter_list_stream_buffer(filters, in.ptr, in.size, target);
1197 }
1198
1199 int git_filter_init(git_filter *filter, unsigned int version)
1200 {
1201 GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT);
1202 return 0;
1203 }
1204
1205 #ifndef GIT_DEPRECATE_HARD
1206
1207 int git_filter_list_stream_data(
1208 git_filter_list *filters,
1209 git_buf *data,
1210 git_writestream *target)
1211 {
1212 return git_filter_list_stream_buffer(filters, data->ptr, data->size, target);
1213 }
1214
1215 int git_filter_list_apply_to_data(
1216 git_buf *tgt, git_filter_list *filters, git_buf *src)
1217 {
1218 return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size);
1219 }
1220
1221 #endif