File Coverage

deps/libgit2/src/libgit2/filter.c
Criterion Covered Total %
statement 410 550 74.5
branch 194 366 53.0
condition n/a
subroutine n/a
pod n/a
total 604 916 65.9


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