]> git.proxmox.com Git - libgit2.git/blame - src/libgit2/filter.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / src / libgit2 / filter.c
CommitLineData
44b1ff4c 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
44b1ff4c
VM
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
eae0bfdc
PP
8#include "filter.h"
9
e579e0f7 10#include "buf.h"
44b1ff4c 11#include "common.h"
22a2d3d5 12#include "futils.h"
44b1ff4c 13#include "hash.h"
c5266eba 14#include "repository.h"
c25aa7cd 15#include "runtime.h"
2a7d224f 16#include "git2/sys/filter.h"
c5266eba 17#include "git2/config.h"
9587895f 18#include "blob.h"
974774c7 19#include "attr_file.h"
2a7d224f 20#include "array.h"
e579e0f7 21#include "path.h"
44b1ff4c 22
570ba25c 23struct git_filter_source {
c25aa7cd
PP
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;
570ba25c
RB
30};
31
85d54812 32typedef struct {
2eecc288 33 const char *filter_name;
85d54812
RB
34 git_filter *filter;
35 void *payload;
36} git_filter_entry;
37
38struct git_filter_list {
39 git_array_t(git_filter_entry) filters;
85d54812 40 git_filter_source source;
e579e0f7 41 git_str *temp_buf;
85d54812
RB
42 char path[GIT_FLEX_ARRAY];
43};
44
45typedef struct {
5623e627 46 char *filter_name;
85d54812 47 git_filter *filter;
974774c7 48 int priority;
29e92d38 49 int initialized;
974774c7
RB
50 size_t nattrs, nmatches;
51 char *attrdata;
52 const char *attrs[GIT_FLEX_ARRAY];
85d54812
RB
53} git_filter_def;
54
974774c7
RB
55static 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
2ed855a9
ET
62struct git_filter_registry {
63 git_rwlock lock;
0646634e 64 git_vector filters;
974774c7
RB
65};
66
2ed855a9 67static struct git_filter_registry filter_registry;
0646634e 68
2ed855a9 69static void git_filter_global_shutdown(void);
4b11f25a 70
0646634e 71
974774c7 72static int filter_def_scan_attrs(
e579e0f7 73 git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
974774c7
RB
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)++;
4b11f25a 93 if (has_eq || *start == '-' || *start == '+' || *start == '!')
974774c7
RB
94 (*nmatch)++;
95
96 if (has_eq)
e579e0f7
MB
97 git_str_putc(attrs, '=');
98 git_str_put(attrs, start, scan - start);
99 git_str_putc(attrs, '\0');
974774c7
RB
100 }
101 }
102
103 return 0;
104}
105
106static 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
0646634e
RB
138static 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;
40cb40fa
RB
142 return name ? git__strcmp(key, name) : -1;
143}
144
145static 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;
0646634e
RB
149}
150
2ed855a9
ET
151/* Note: callers must lock the registry before calling this function */
152static int filter_registry_insert(
974774c7
RB
153 const char *name, git_filter *filter, int priority)
154{
155 git_filter_def *fdef;
392702ee 156 size_t nattr = 0, nmatch = 0, alloc_len;
e579e0f7 157 git_str attrs = GIT_STR_INIT;
974774c7 158
974774c7
RB
159 if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
160 return -1;
161
ac3d33df
JK
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));
392702ee
ET
165
166 fdef = git__calloc(1, alloc_len);
ac3d33df 167 GIT_ERROR_CHECK_ALLOC(fdef);
974774c7 168
5623e627 169 fdef->filter_name = git__strdup(name);
ac3d33df 170 GIT_ERROR_CHECK_ALLOC(fdef->filter_name);
5623e627 171
974774c7
RB
172 fdef->filter = filter;
173 fdef->priority = priority;
174 fdef->nattrs = nattr;
175 fdef->nmatches = nmatch;
e579e0f7 176 fdef->attrdata = git_str_detach(&attrs);
974774c7
RB
177
178 filter_def_set_attrs(fdef);
179
2ed855a9 180 if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
5623e627 181 git__free(fdef->filter_name);
974774c7
RB
182 git__free(fdef->attrdata);
183 git__free(fdef);
184 return -1;
185 }
186
2ed855a9 187 git_vector_sort(&filter_registry.filters);
29e92d38
RB
188 return 0;
189}
190
2ed855a9
ET
191int 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
c25aa7cd
PP
211 if (!error)
212 error = git_runtime_shutdown_register(git_filter_global_shutdown);
2ed855a9
ET
213
214done:
215 if (error) {
216 git_filter_free(crlf);
217 git_filter_free(ident);
218 }
219
220 return error;
221}
222
223static 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 */
249static 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 */
256static 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
267int git_filter_register(
268 const char *name, git_filter *filter, int priority)
269{
270 int error;
271
c25aa7cd
PP
272 GIT_ASSERT_ARG(name);
273 GIT_ASSERT_ARG(filter);
2ed855a9
ET
274
275 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
ac3d33df 276 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
2ed855a9
ET
277 return -1;
278 }
279
280 if (!filter_registry_find(NULL, name)) {
ac3d33df
JK
281 git_error_set(
282 GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name);
2ed855a9
ET
283 error = GIT_EEXISTS;
284 goto done;
285 }
286
287 error = filter_registry_insert(name, filter, priority);
288
289done:
290 git_rwlock_wrunlock(&filter_registry.lock);
291 return error;
292}
293
974774c7
RB
294int git_filter_unregister(const char *name)
295{
296 size_t pos;
297 git_filter_def *fdef;
2ed855a9 298 int error = 0;
974774c7 299
c25aa7cd 300 GIT_ASSERT_ARG(name);
5623e627 301
974774c7 302 /* cannot unregister default filters */
eefc32d5 303 if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
ac3d33df 304 git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name);
974774c7
RB
305 return -1;
306 }
307
2ed855a9 308 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
ac3d33df 309 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
2ed855a9
ET
310 return -1;
311 }
312
0646634e 313 if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
ac3d33df 314 git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name);
2ed855a9
ET
315 error = GIT_ENOTFOUND;
316 goto done;
974774c7
RB
317 }
318
2ed855a9 319 git_vector_remove(&filter_registry.filters, pos);
974774c7 320
29e92d38 321 if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
974774c7 322 fdef->filter->shutdown(fdef->filter);
29e92d38
RB
323 fdef->initialized = false;
324 }
974774c7 325
5623e627 326 git__free(fdef->filter_name);
974774c7
RB
327 git__free(fdef->attrdata);
328 git__free(fdef);
329
2ed855a9
ET
330done:
331 git_rwlock_wrunlock(&filter_registry.lock);
332 return error;
974774c7
RB
333}
334
0646634e
RB
335static int filter_initialize(git_filter_def *fdef)
336{
337 int error = 0;
338
2ed855a9
ET
339 if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
340 if ((error = fdef->filter->initialize(fdef->filter)) < 0)
341 return error;
0646634e
RB
342 }
343
344 fdef->initialized = true;
345 return 0;
346}
347
974774c7
RB
348git_filter *git_filter_lookup(const char *name)
349{
350 size_t pos;
0646634e 351 git_filter_def *fdef;
2ed855a9 352 git_filter *filter = NULL;
0646634e 353
2ed855a9 354 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
ac3d33df 355 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
0646634e 356 return NULL;
2ed855a9 357 }
29e92d38 358
2ed855a9
ET
359 if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
360 (!fdef->initialized && filter_initialize(fdef) < 0))
361 goto done;
29e92d38 362
2ed855a9 363 filter = fdef->filter;
29e92d38 364
2ed855a9
ET
365done:
366 git_rwlock_rdunlock(&filter_registry.lock);
367 return filter;
974774c7
RB
368}
369
4b11f25a
RB
370void git_filter_free(git_filter *filter)
371{
372 git__free(filter);
373}
374
570ba25c
RB
375git_repository *git_filter_source_repo(const git_filter_source *src)
376{
377 return src->repo;
378}
379
380const char *git_filter_source_path(const git_filter_source *src)
381{
382 return src->path;
383}
384
385uint16_t git_filter_source_filemode(const git_filter_source *src)
386{
387 return src->filemode;
388}
389
390const git_oid *git_filter_source_id(const git_filter_source *src)
391{
22a2d3d5 392 return git_oid_is_zero(&src->oid) ? NULL : &src->oid;
570ba25c
RB
393}
394
2a7d224f
RB
395git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
396{
397 return src->mode;
398}
399
795eaccd 400uint32_t git_filter_source_flags(const git_filter_source *src)
5269008c 401{
c25aa7cd 402 return src->options.flags;
5269008c
RB
403}
404
40cb40fa 405static int filter_list_new(
2a7d224f 406 git_filter_list **out, const git_filter_source *src)
27950fa3 407{
85d54812 408 git_filter_list *fl = NULL;
f1453c59 409 size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
85d54812 410
ac3d33df
JK
411 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
412 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
392702ee 413
f1453c59 414 fl = git__calloc(1, alloclen);
ac3d33df 415 GIT_ERROR_CHECK_ALLOC(fl);
85d54812 416
85d54812
RB
417 if (src->path)
418 memcpy(fl->path, src->path, pathlen);
419 fl->source.repo = src->repo;
420 fl->source.path = fl->path;
2a7d224f 421 fl->source.mode = src->mode;
c25aa7cd
PP
422
423 memcpy(&fl->source.options, &src->options, sizeof(git_filter_options));
85d54812
RB
424
425 *out = fl;
426 return 0;
427}
428
974774c7 429static int filter_list_check_attributes(
9f779aac
ET
430 const char ***out,
431 git_repository *repo,
c25aa7cd 432 git_filter_session *filter_session,
9f779aac
ET
433 git_filter_def *fdef,
434 const git_filter_source *src)
974774c7 435{
974774c7 436 const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
c25aa7cd 437 git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT;
22a2d3d5
UG
438 size_t i;
439 int error;
440
ac3d33df 441 GIT_ERROR_CHECK_ALLOC(strs);
974774c7 442
c25aa7cd
PP
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;
22a2d3d5 451
c25aa7cd
PP
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 }
22a2d3d5 459
9f779aac 460 error = git_attr_get_many_with_session(
c25aa7cd 461 strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs);
974774c7
RB
462
463 /* if no values were found but no matches are needed, it's okay! */
464 if (error == GIT_ENOTFOUND && !fdef->nmatches) {
ac3d33df 465 git_error_clear();
e399c7ee 466 git__free((void *)strs);
974774c7
RB
467 return 0;
468 }
469
470 for (i = 0; !error && i < fdef->nattrs; ++i) {
471 const char *want = fdef->attrs[fdef->nattrs + i];
22a2d3d5 472 git_attr_value_t want_type, found_type;
974774c7
RB
473
474 if (!want)
475 continue;
476
477 want_type = git_attr_value(want);
478 found_type = git_attr_value(strs[i]);
479
63924435
ET
480 if (want_type != found_type)
481 error = GIT_ENOTFOUND;
22a2d3d5 482 else if (want_type == GIT_ATTR_VALUE_STRING &&
63924435
ET
483 strcmp(want, strs[i]) &&
484 strcmp(want, "*"))
974774c7
RB
485 error = GIT_ENOTFOUND;
486 }
487
488 if (error)
e399c7ee 489 git__free((void *)strs);
974774c7
RB
490 else
491 *out = strs;
492
493 return error;
494}
495
40cb40fa 496int git_filter_list_new(
5269008c
RB
497 git_filter_list **out,
498 git_repository *repo,
499 git_filter_mode_t mode,
795eaccd 500 uint32_t flags)
40cb40fa
RB
501{
502 git_filter_source src = { 0 };
503 src.repo = repo;
504 src.path = NULL;
505 src.mode = mode;
c25aa7cd 506 src.options.flags = flags;
40cb40fa
RB
507 return filter_list_new(out, &src);
508}
509
c25aa7cd 510int git_filter_list__load(
85d54812
RB
511 git_filter_list **filters,
512 git_repository *repo,
4b11f25a 513 git_blob *blob, /* can be NULL */
85d54812 514 const char *path,
5269008c 515 git_filter_mode_t mode,
c25aa7cd 516 git_filter_session *filter_session)
85d54812
RB
517{
518 int error = 0;
519 git_filter_list *fl = NULL;
520 git_filter_source src = { 0 };
521 git_filter_entry *fe;
974774c7
RB
522 size_t idx;
523 git_filter_def *fdef;
85d54812 524
2ed855a9 525 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
ac3d33df 526 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
85d54812 527 return -1;
2ed855a9 528 }
85d54812
RB
529
530 src.repo = repo;
531 src.path = path;
2a7d224f 532 src.mode = mode;
c25aa7cd
PP
533
534 memcpy(&src.options, &filter_session->options, sizeof(git_filter_options));
d05218b0 535
4b11f25a
RB
536 if (blob)
537 git_oid_cpy(&src.oid, git_blob_id(blob));
85d54812 538
2ed855a9 539 git_vector_foreach(&filter_registry.filters, idx, fdef) {
974774c7 540 const char **values = NULL;
85d54812 541 void *payload = NULL;
85d54812
RB
542
543 if (!fdef || !fdef->filter)
544 continue;
27950fa3 545
974774c7 546 if (fdef->nattrs > 0) {
9f779aac 547 error = filter_list_check_attributes(
c25aa7cd
PP
548 &values, repo,
549 filter_session, fdef, &src);
9f779aac 550
974774c7
RB
551 if (error == GIT_ENOTFOUND) {
552 error = 0;
553 continue;
554 } else if (error < 0)
555 break;
556 }
557
29e92d38
RB
558 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
559 break;
560
85d54812 561 if (fdef->filter->check)
974774c7 562 error = fdef->filter->check(
2a7d224f 563 fdef->filter, &payload, &src, values);
974774c7 564
e399c7ee 565 git__free((void *)values);
85d54812 566
eefc32d5 567 if (error == GIT_PASSTHROUGH)
85d54812
RB
568 error = 0;
569 else if (error < 0)
570 break;
571 else {
9c9aa1ba
ET
572 if (!fl) {
573 if ((error = filter_list_new(&fl, &src)) < 0)
2ed855a9 574 break;
9c9aa1ba 575
c25aa7cd 576 fl->temp_buf = filter_session->temp_buf;
9c9aa1ba 577 }
85d54812
RB
578
579 fe = git_array_alloc(fl->filters);
ac3d33df 580 GIT_ERROR_CHECK_ALLOC(fe);
2eecc288
ET
581
582 fe->filter = fdef->filter;
583 fe->filter_name = fdef->filter_name;
85d54812
RB
584 fe->payload = payload;
585 }
586 }
587
2ed855a9
ET
588 git_rwlock_rdunlock(&filter_registry.lock);
589
85d54812
RB
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
c25aa7cd
PP
600int 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
9f779aac
ET
617int 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,
795eaccd 623 uint32_t flags)
9f779aac 624{
c25aa7cd 625 git_filter_session filter_session = GIT_FILTER_SESSION_INIT;
d05218b0 626
c25aa7cd 627 filter_session.options.flags = flags;
d05218b0 628
c25aa7cd
PP
629 return git_filter_list__load(
630 filters, repo, blob, path, mode, &filter_session);
9f779aac
ET
631}
632
85d54812
RB
633void 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);
27950fa3
VM
644 }
645
85d54812
RB
646 git_array_clear(fl->filters);
647 git__free(fl);
27950fa3
VM
648}
649
2eecc288
ET
650int git_filter_list_contains(
651 git_filter_list *fl,
652 const char *name)
653{
654 size_t i;
655
c25aa7cd 656 GIT_ASSERT_ARG(name);
2eecc288
ET
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
40cb40fa
RB
669int git_filter_list_push(
670 git_filter_list *fl, git_filter *filter, void *payload)
671{
672 int error = 0;
673 size_t pos;
2ed855a9 674 git_filter_def *fdef = NULL;
40cb40fa
RB
675 git_filter_entry *fe;
676
c25aa7cd
PP
677 GIT_ASSERT_ARG(fl);
678 GIT_ASSERT_ARG(filter);
40cb40fa 679
2ed855a9 680 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
ac3d33df 681 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
2ed855a9
ET
682 return -1;
683 }
684
40cb40fa 685 if (git_vector_search2(
2ed855a9
ET
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) {
ac3d33df 693 git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter");
40cb40fa
RB
694 return -1;
695 }
696
40cb40fa
RB
697 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
698 return error;
699
700 fe = git_array_alloc(fl->filters);
ac3d33df 701 GIT_ERROR_CHECK_ALLOC(fe);
40cb40fa
RB
702 fe->filter = filter;
703 fe->payload = payload;
704
705 return 0;
706}
707
b47349b8
RB
708size_t git_filter_list_length(const git_filter_list *fl)
709{
710 return fl ? git_array_size(fl->filters) : 0;
711}
712
5555696f 713struct buf_stream {
f7c0125f 714 git_writestream parent;
e579e0f7 715 git_str *target;
5555696f
ET
716 bool complete;
717};
718
719static int buf_stream_write(
b75f15aa 720 git_writestream *s, const char *buffer, size_t len)
44b1ff4c 721{
5555696f 722 struct buf_stream *buf_stream = (struct buf_stream *)s;
c25aa7cd
PP
723 GIT_ASSERT_ARG(buf_stream);
724 GIT_ASSERT(buf_stream->complete == 0);
1e4976cb 725
e579e0f7 726 return git_str_put(buf_stream->target, buffer, len);
5555696f 727}
44b1ff4c 728
b75f15aa 729static int buf_stream_close(git_writestream *s)
5555696f
ET
730{
731 struct buf_stream *buf_stream = (struct buf_stream *)s;
c25aa7cd 732 GIT_ASSERT_ARG(buf_stream);
44b1ff4c 733
c25aa7cd 734 GIT_ASSERT(buf_stream->complete == 0);
5555696f 735 buf_stream->complete = 1;
44b1ff4c 736
5555696f
ET
737 return 0;
738}
85d54812 739
b75f15aa 740static void buf_stream_free(git_writestream *s)
5555696f
ET
741{
742 GIT_UNUSED(s);
743}
85d54812 744
e579e0f7 745static void buf_stream_init(struct buf_stream *writer, git_str *target)
5555696f
ET
746{
747 memset(writer, 0, sizeof(struct buf_stream));
44b1ff4c 748
f7c0125f
ET
749 writer->parent.write = buf_stream_write;
750 writer->parent.close = buf_stream_close;
751 writer->parent.free = buf_stream_free;
5555696f 752 writer->target = target;
2a7d224f 753
e579e0f7 754 git_str_clear(target);
5555696f 755}
44b1ff4c 756
c25aa7cd
PP
757int git_filter_list_apply_to_buffer(
758 git_buf *out,
759 git_filter_list *filters,
760 const char *in,
761 size_t in_len)
e579e0f7
MB
762{
763 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len);
764}
765
766int git_filter_list__apply_to_buffer(
767 git_str *out,
768 git_filter_list *filters,
769 const char *in,
770 size_t in_len)
5555696f
ET
771{
772 struct buf_stream writer;
773 int error;
774
c25aa7cd
PP
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
785int git_filter_list__convert_buf(
e579e0f7 786 git_str *out,
c25aa7cd 787 git_filter_list *filters,
e579e0f7 788 git_str *in)
c25aa7cd
PP
789{
790 int error;
791
792 if (!filters || git_filter_list_length(filters) == 0) {
e579e0f7
MB
793 git_str_swap(out, in);
794 git_str_dispose(in);
d4cf1675
ET
795 return 0;
796 }
5555696f 797
e579e0f7 798 error = git_filter_list__apply_to_buffer(out, filters,
c25aa7cd 799 in->ptr, in->size);
5555696f 800
c25aa7cd 801 if (!error)
e579e0f7 802 git_str_dispose(in);
5555696f 803
424222f4 804 return error;
44b1ff4c 805}
2a7d224f
RB
806
807int git_filter_list_apply_to_file(
a9f51e43 808 git_buf *out,
2a7d224f
RB
809 git_filter_list *filters,
810 git_repository *repo,
811 const char *path)
e579e0f7
MB
812{
813 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path);
814}
815
816int git_filter_list__apply_to_file(
817 git_str *out,
818 git_filter_list *filters,
819 git_repository *repo,
820 const char *path)
2a7d224f 821{
5555696f 822 struct buf_stream writer;
2a7d224f 823 int error;
2a7d224f 824
5555696f 825 buf_stream_init(&writer, out);
2a7d224f 826
5555696f 827 if ((error = git_filter_list_stream_file(
69f0032b 828 filters, repo, path, &writer.parent)) < 0)
5555696f 829 return error;
2a7d224f 830
c25aa7cd 831 GIT_ASSERT(writer.complete);
2a7d224f
RB
832 return error;
833}
834
e579e0f7 835static int buf_from_blob(git_str *out, git_blob *blob)
fbdc9db3 836{
22a2d3d5 837 git_object_size_t rawsize = git_blob_rawsize(blob);
fbdc9db3
ET
838
839 if (!git__is_sizet(rawsize)) {
ac3d33df 840 git_error_set(GIT_ERROR_OS, "blob is too large to filter");
fbdc9db3
ET
841 return -1;
842 }
843
e579e0f7 844 git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
fbdc9db3
ET
845 return 0;
846}
847
2a7d224f 848int git_filter_list_apply_to_blob(
a9f51e43 849 git_buf *out,
2a7d224f
RB
850 git_filter_list *filters,
851 git_blob *blob)
e579e0f7
MB
852{
853 GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob);
854}
855
856int git_filter_list__apply_to_blob(
857 git_str *out,
858 git_filter_list *filters,
859 git_blob *blob)
2a7d224f 860{
5555696f
ET
861 struct buf_stream writer;
862 int error;
71379313 863
5555696f 864 buf_stream_init(&writer, out);
fbdc9db3 865
5555696f 866 if ((error = git_filter_list_stream_blob(
69f0032b 867 filters, blob, &writer.parent)) < 0)
5555696f 868 return error;
fbdc9db3 869
c25aa7cd 870 GIT_ASSERT(writer.complete);
5555696f 871 return error;
fbdc9db3
ET
872}
873
c25aa7cd 874struct buffered_stream {
f7c0125f 875 git_writestream parent;
fbdc9db3 876 git_filter *filter;
e579e0f7
MB
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 *);
fbdc9db3
ET
879 const git_filter_source *source;
880 void **payload;
e579e0f7
MB
881 git_str input;
882 git_str temp_buf;
883 git_str *output;
b75f15aa 884 git_writestream *target;
fbdc9db3
ET
885};
886
c25aa7cd 887static int buffered_stream_write(
b75f15aa 888 git_writestream *s, const char *buffer, size_t len)
fbdc9db3 889{
c25aa7cd
PP
890 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
891 GIT_ASSERT_ARG(buffered_stream);
fbdc9db3 892
e579e0f7 893 return git_str_put(&buffered_stream->input, buffer, len);
fbdc9db3
ET
894}
895
ad5611d8
TR
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
c25aa7cd 907static int buffered_stream_close(git_writestream *s)
fbdc9db3 908{
c25aa7cd 909 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
e579e0f7 910 git_str *writebuf;
ac3d33df 911 git_error_state error_state = {0};
fbdc9db3
ET
912 int error;
913
c25aa7cd 914 GIT_ASSERT_ARG(buffered_stream);
fbdc9db3 915
ad5611d8
TR
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
c25aa7cd
PP
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);
fbdc9db3
ET
941
942 if (error == GIT_PASSTHROUGH) {
c25aa7cd 943 writebuf = &buffered_stream->input;
fbdc9db3 944 } else if (error == 0) {
c25aa7cd 945 writebuf = buffered_stream->output;
fbdc9db3 946 } else {
ac3d33df
JK
947 /* close stream before erroring out taking care
948 * to preserve the original error */
949 git_error_state_capture(&error_state, error);
c25aa7cd 950 buffered_stream->target->close(buffered_stream->target);
ac3d33df 951 git_error_state_restore(&error_state);
fbdc9db3
ET
952 return error;
953 }
954
c25aa7cd
PP
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);
fbdc9db3
ET
958
959 return error;
960}
961
c25aa7cd 962static void buffered_stream_free(git_writestream *s)
fbdc9db3 963{
c25aa7cd 964 struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
fbdc9db3 965
c25aa7cd 966 if (buffered_stream) {
e579e0f7
MB
967 git_str_dispose(&buffered_stream->input);
968 git_str_dispose(&buffered_stream->temp_buf);
c25aa7cd
PP
969 git__free(buffered_stream);
970 }
fbdc9db3
ET
971}
972
c25aa7cd 973int git_filter_buffered_stream_new(
b75f15aa 974 git_writestream **out,
fbdc9db3 975 git_filter *filter,
e579e0f7
MB
976 int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *),
977 git_str *temp_buf,
fbdc9db3
ET
978 void **payload,
979 const git_filter_source *source,
b75f15aa 980 git_writestream *target)
fbdc9db3 981{
c25aa7cd
PP
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;
fbdc9db3 994
669ae274 995 if (temp_buf)
e579e0f7 996 git_str_clear(temp_buf);
669ae274 997
c25aa7cd 998 *out = (git_writestream *)buffered_stream;
fbdc9db3
ET
999 return 0;
1000}
1001
e579e0f7
MB
1002#ifndef GIT_DEPRECATE_HARD
1003static 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
c25aa7cd
PP
1033static 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 */
e579e0f7 1048 return buffered_legacy_stream_new(out,
c25aa7cd
PP
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
fbdc9db3 1059static int stream_list_init(
b75f15aa 1060 git_writestream **out,
fbdc9db3
ET
1061 git_vector *streams,
1062 git_filter_list *filters,
b75f15aa 1063 git_writestream *target)
fbdc9db3 1064{
b75f15aa 1065 git_writestream *last_stream = target;
fbdc9db3
ET
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;
c25aa7cd 1080
fbdc9db3 1081 git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
b75f15aa 1082 git_writestream *filter_stream;
a78441bc 1083
c25aa7cd 1084 error = setup_stream(&filter_stream, fe, filters, last_stream);
fbdc9db3 1085
5555696f 1086 if (error < 0)
cf07db2f 1087 goto out;
5555696f
ET
1088
1089 git_vector_insert(streams, filter_stream);
fbdc9db3 1090 last_stream = filter_stream;
71379313
RB
1091 }
1092
cf07db2f
PS
1093out:
1094 if (error)
1095 last_stream->close(last_stream);
1096 else
1097 *out = last_stream;
1098
1099 return error;
fbdc9db3
ET
1100}
1101
22a2d3d5 1102static void filter_streams_free(git_vector *streams)
fbdc9db3 1103{
b75f15aa 1104 git_writestream *stream;
fbdc9db3
ET
1105 size_t i;
1106
1107 git_vector_foreach(streams, i, stream)
1108 stream->free(stream);
5555696f 1109 git_vector_free(streams);
fbdc9db3
ET
1110}
1111
fbdc9db3
ET
1112int git_filter_list_stream_file(
1113 git_filter_list *filters,
fbdc9db3
ET
1114 git_repository *repo,
1115 const char *path,
b75f15aa 1116 git_writestream *target)
fbdc9db3 1117{
ad5611d8 1118 char buf[GIT_BUFSIZE_FILTERIO];
e579e0f7 1119 git_str abspath = GIT_STR_INIT;
fbdc9db3
ET
1120 const char *base = repo ? git_repository_workdir(repo) : NULL;
1121 git_vector filter_streams = GIT_VECTOR_INIT;
b75f15aa 1122 git_writestream *stream_start;
fbdc9db3 1123 ssize_t readlen;
cf07db2f 1124 int fd = -1, error, initialized = 0;
fbdc9db3
ET
1125
1126 if ((error = stream_list_init(
1127 &stream_start, &filter_streams, filters, target)) < 0 ||
e579e0f7
MB
1128 (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 ||
1129 (error = git_path_validate_str_length(repo, &abspath)) < 0)
fbdc9db3 1130 goto done;
c25aa7cd 1131
cf07db2f 1132 initialized = 1;
fbdc9db3 1133
6a2edc5a 1134 if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
fbdc9db3
ET
1135 error = fd;
1136 goto done;
1137 }
1138
7dd22538 1139 while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
5555696f 1140 if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
fbdc9db3
ET
1141 goto done;
1142 }
1143
cf07db2f 1144 if (readlen < 0)
ac3d33df 1145 error = -1;
fbdc9db3 1146
fbdc9db3 1147done:
cf07db2f
PS
1148 if (initialized)
1149 error |= stream_start->close(stream_start);
1150
0137aba5
CMN
1151 if (fd >= 0)
1152 p_close(fd);
22a2d3d5 1153 filter_streams_free(&filter_streams);
e579e0f7 1154 git_str_dispose(&abspath);
fbdc9db3
ET
1155 return error;
1156}
1157
c25aa7cd 1158int git_filter_list_stream_buffer(
fbdc9db3 1159 git_filter_list *filters,
c25aa7cd
PP
1160 const char *buffer,
1161 size_t len,
b75f15aa 1162 git_writestream *target)
fbdc9db3
ET
1163{
1164 git_vector filter_streams = GIT_VECTOR_INIT;
b75f15aa 1165 git_writestream *stream_start;
cf07db2f 1166 int error, initialized = 0;
fbdc9db3 1167
4de7f3bf
CMN
1168 if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
1169 goto out;
cf07db2f 1170 initialized = 1;
fbdc9db3 1171
c25aa7cd 1172 if ((error = stream_start->write(stream_start, buffer, len)) < 0)
cf07db2f 1173 goto out;
4de7f3bf
CMN
1174
1175out:
cf07db2f
PS
1176 if (initialized)
1177 error |= stream_start->close(stream_start);
1178
22a2d3d5 1179 filter_streams_free(&filter_streams);
cf07db2f 1180 return error;
fbdc9db3
ET
1181}
1182
1183int git_filter_list_stream_blob(
1184 git_filter_list *filters,
1185 git_blob *blob,
b75f15aa 1186 git_writestream *target)
fbdc9db3 1187{
e579e0f7 1188 git_str in = GIT_STR_INIT;
fbdc9db3
ET
1189
1190 if (buf_from_blob(&in, blob) < 0)
1191 return -1;
2a7d224f 1192
4b11f25a
RB
1193 if (filters)
1194 git_oid_cpy(&filters->source.oid, git_blob_id(blob));
1195
c25aa7cd 1196 return git_filter_list_stream_buffer(filters, in.ptr, in.size, target);
2a7d224f 1197}
a78441bc
MM
1198
1199int 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}
c25aa7cd
PP
1204
1205#ifndef GIT_DEPRECATE_HARD
1206
1207int git_filter_list_stream_data(
1208 git_filter_list *filters,
1209 git_buf *data,
1210 git_writestream *target)
1211{
c25aa7cd
PP
1212 return git_filter_list_stream_buffer(filters, data->ptr, data->size, target);
1213}
1214
1215int git_filter_list_apply_to_data(
1216 git_buf *tgt, git_filter_list *filters, git_buf *src)
1217{
c25aa7cd
PP
1218 return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size);
1219}
1220
1221#endif