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