filter.c 26.7 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "filter.h"

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 25 26 27 28 29
	git_repository    *repo;
	const char        *path;
	git_oid            oid;  /* zero if unknown (which is likely) */
	uint16_t           filemode; /* zero if unknown */
	git_filter_mode_t  mode;
	git_filter_options options;
30 31
};

32
typedef struct {
33
	const char *filter_name;
34 35 36 37 38 39 40
	git_filter *filter;
	void *payload;
} git_filter_entry;

struct git_filter_list {
	git_array_t(git_filter_entry) filters;
	git_filter_source source;
41
	git_str *temp_buf;
42 43 44 45
	char path[GIT_FLEX_ARRAY];
};

typedef struct {
46
	char *filter_name;
47
	git_filter *filter;
48
	int priority;
49
	int initialized;
50 51 52
	size_t nattrs, nmatches;
	char *attrdata;
	const char *attrs[GIT_FLEX_ARRAY];
53 54
} git_filter_def;

55 56 57 58 59 60 61
static int filter_def_priority_cmp(const void *a, const void *b)
{
	int pa = ((const git_filter_def *)a)->priority;
	int pb = ((const git_filter_def *)b)->priority;
	return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
}

62 63
struct git_filter_registry {
	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);
Russell Belfer committed
70

71

72
static int filter_def_scan_attrs(
73
	git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
{
	const char *start, *scan = attr_str;
	int has_eq;

	*nattr = *nmatch = 0;

	if (!scan)
		return 0;

	while (*scan) {
		while (git__isspace(*scan)) scan++;

		for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
			if (*scan == '=')
				has_eq = 1;
		}

		if (scan > start) {
			(*nattr)++;
Russell Belfer committed
93
			if (has_eq || *start == '-' || *start == '+' || *start == '!')
94 95 96
				(*nmatch)++;

			if (has_eq)
97 98 99
				git_str_putc(attrs, '=');
			git_str_put(attrs, start, scan - start);
			git_str_putc(attrs, '\0');
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
		}
	}

	return 0;
}

static void filter_def_set_attrs(git_filter_def *fdef)
{
	char *scan = fdef->attrdata;
	size_t i;

	for (i = 0; i < fdef->nattrs; ++i) {
		const char *name, *value;

		switch (*scan) {
		case '=':
			name = scan + 1;
			for (scan++; *scan != '='; scan++) /* find '=' */;
			*scan++ = '\0';
			value = scan;
			break;
		case '-':
			name = scan + 1; value = git_attr__false; break;
		case '+':
			name = scan + 1; value = git_attr__true;  break;
		case '!':
			name = scan + 1; value = git_attr__unset; break;
		default:
			name = scan;     value = NULL; break;
		}

		fdef->attrs[i] = name;
		fdef->attrs[i + fdef->nattrs] = value;

		scan += strlen(scan) + 1;
	}
}

138 139 140 141
static int filter_def_name_key_check(const void *key, const void *fdef)
{
	const char *name =
		fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
142 143 144 145 146 147 148
	return name ? git__strcmp(key, name) : -1;
}

static int filter_def_filter_key_check(const void *key, const void *fdef)
{
	const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
	return (key == filter) ? 0 : -1;
149 150
}

151 152
/* Note: callers must lock the registry before calling this function */
static int filter_registry_insert(
153 154 155
	const char *name, git_filter *filter, int priority)
{
	git_filter_def *fdef;
156
	size_t nattr = 0, nmatch = 0, alloc_len;
157
	git_str attrs = GIT_STR_INIT;
158 159 160 161

	if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
		return -1;

162 163 164
	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2);
	GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *));
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def));
165 166

	fdef = git__calloc(1, alloc_len);
167
	GIT_ERROR_CHECK_ALLOC(fdef);
168

169
	fdef->filter_name = git__strdup(name);
170
	GIT_ERROR_CHECK_ALLOC(fdef->filter_name);
171

172 173 174 175
	fdef->filter      = filter;
	fdef->priority    = priority;
	fdef->nattrs      = nattr;
	fdef->nmatches    = nmatch;
176
	fdef->attrdata    = git_str_detach(&attrs);
177 178 179

	filter_def_set_attrs(fdef);

180
	if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
181
		git__free(fdef->filter_name);
182 183 184 185 186
		git__free(fdef->attrdata);
		git__free(fdef);
		return -1;
	}

187
	git_vector_sort(&filter_registry.filters);
188 189 190
	return 0;
}

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
int git_filter_global_init(void)
{
	git_filter *crlf = NULL, *ident = NULL;
	int error = 0;

	if (git_rwlock_init(&filter_registry.lock) < 0)
		return -1;

	if ((error = git_vector_init(&filter_registry.filters, 2,
		filter_def_priority_cmp)) < 0)
		goto done;

	if ((crlf = git_crlf_filter_new()) == NULL ||
		filter_registry_insert(
			GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 ||
		(ident = git_ident_filter_new()) == NULL ||
		filter_registry_insert(
			GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
		error = -1;

211 212
	if (!error)
		error = git_runtime_shutdown_register(git_filter_global_shutdown);
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

done:
	if (error) {
		git_filter_free(crlf);
		git_filter_free(ident);
	}

	return error;
}

static void git_filter_global_shutdown(void)
{
	size_t pos;
	git_filter_def *fdef;

	if (git_rwlock_wrlock(&filter_registry.lock) < 0)
		return;

	git_vector_foreach(&filter_registry.filters, pos, fdef) {
		if (fdef->filter && fdef->filter->shutdown) {
			fdef->filter->shutdown(fdef->filter);
			fdef->initialized = false;
		}

		git__free(fdef->filter_name);
		git__free(fdef->attrdata);
		git__free(fdef);
	}

	git_vector_free(&filter_registry.filters);

	git_rwlock_wrunlock(&filter_registry.lock);
	git_rwlock_free(&filter_registry.lock);
}

/* Note: callers must lock the registry before calling this function */
static int filter_registry_find(size_t *pos, const char *name)
{
	return git_vector_search2(
		pos, &filter_registry.filters, filter_def_name_key_check, name);
}

/* Note: callers must lock the registry before calling this function */
static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
{
	git_filter_def *fdef = NULL;

	if (!filter_registry_find(pos, name))
		fdef = git_vector_get(&filter_registry.filters, *pos);

	return fdef;
}


int git_filter_register(
	const char *name, git_filter *filter, int priority)
{
	int error;

272 273
	GIT_ASSERT_ARG(name);
	GIT_ASSERT_ARG(filter);
274 275

	if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
276
		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
277 278 279 280
		return -1;
	}

	if (!filter_registry_find(NULL, name)) {
281 282
		git_error_set(
			GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name);
283 284 285 286 287 288 289 290 291 292 293
		error = GIT_EEXISTS;
		goto done;
	}

	error = filter_registry_insert(name, filter, priority);

done:
	git_rwlock_wrunlock(&filter_registry.lock);
	return error;
}

294 295 296 297
int git_filter_unregister(const char *name)
{
	size_t pos;
	git_filter_def *fdef;
298
	int error = 0;
299

300
	GIT_ASSERT_ARG(name);
301

302
	/* cannot unregister default filters */
Russell Belfer committed
303
	if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
304
		git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name);
305 306 307
		return -1;
	}

308
	if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
309
		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
310 311 312
		return -1;
	}

313
	if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
314
		git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name);
315 316
		error = GIT_ENOTFOUND;
		goto done;
317 318
	}

319
	git_vector_remove(&filter_registry.filters, pos);
320

321
	if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
322
		fdef->filter->shutdown(fdef->filter);
323 324
		fdef->initialized = false;
	}
325

326
	git__free(fdef->filter_name);
327 328 329
	git__free(fdef->attrdata);
	git__free(fdef);

330 331 332
done:
	git_rwlock_wrunlock(&filter_registry.lock);
	return error;
333 334
}

335 336 337 338
static int filter_initialize(git_filter_def *fdef)
{
	int error = 0;

339 340 341
	if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
		if ((error = fdef->filter->initialize(fdef->filter)) < 0)
			return error;
342 343 344 345 346 347
	}

	fdef->initialized = true;
	return 0;
}

348 349 350
git_filter *git_filter_lookup(const char *name)
{
	size_t pos;
351
	git_filter_def *fdef;
352
	git_filter *filter = NULL;
353

354
	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
355
		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
356
		return NULL;
357
	}
358

359 360 361
	if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
		(!fdef->initialized && filter_initialize(fdef) < 0))
		goto done;
362

363
	filter = fdef->filter;
364

365 366 367
done:
	git_rwlock_rdunlock(&filter_registry.lock);
	return filter;
368 369
}

Russell Belfer committed
370 371 372 373 374
void git_filter_free(git_filter *filter)
{
	git__free(filter);
}

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
git_repository *git_filter_source_repo(const git_filter_source *src)
{
	return src->repo;
}

const char *git_filter_source_path(const git_filter_source *src)
{
	return src->path;
}

uint16_t git_filter_source_filemode(const git_filter_source *src)
{
	return src->filemode;
}

const git_oid *git_filter_source_id(const git_filter_source *src)
{
392
	return git_oid_is_zero(&src->oid) ? NULL : &src->oid;
393 394
}

395 396 397 398 399
git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
{
	return src->mode;
}

400
uint32_t git_filter_source_flags(const git_filter_source *src)
401
{
402
	return src->options.flags;
403 404
}

405
static int filter_list_new(
406
	git_filter_list **out, const git_filter_source *src)
407
{
408
	git_filter_list *fl = NULL;
409
	size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
410

411 412
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
	GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
413

414
	fl = git__calloc(1, alloclen);
415
	GIT_ERROR_CHECK_ALLOC(fl);
416 417 418 419 420

	if (src->path)
		memcpy(fl->path, src->path, pathlen);
	fl->source.repo = src->repo;
	fl->source.path = fl->path;
421
	fl->source.mode = src->mode;
422 423

	memcpy(&fl->source.options, &src->options, sizeof(git_filter_options));
424 425 426 427 428

	*out = fl;
	return 0;
}

429
static int filter_list_check_attributes(
430 431
	const char ***out,
	git_repository *repo,
432
	git_filter_session *filter_session,
433 434
	git_filter_def *fdef,
	const git_filter_source *src)
435 436
{
	const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
437
	git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT;
438 439 440
	size_t i;
	int error;

441
	GIT_ERROR_CHECK_ALLOC(strs);
442

443
	if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
444
		attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM;
445

446
	if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0)
447
		attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD;
448

449 450
	if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) {
		attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT;
451 452 453 454 455 456 457

#ifndef GIT_DEPRECATE_HARD
		if (src->options.commit_id)
			git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id);
		else
#endif
		git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id);
458 459
	}

460
	error = git_attr_get_many_with_session(
461
		strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs);
462 463 464

	/* if no values were found but no matches are needed, it's okay! */
	if (error == GIT_ENOTFOUND && !fdef->nmatches) {
465
		git_error_clear();
Russell Belfer committed
466
		git__free((void *)strs);
467 468 469 470 471
		return 0;
	}

	for (i = 0; !error && i < fdef->nattrs; ++i) {
		const char *want = fdef->attrs[fdef->nattrs + i];
472
		git_attr_value_t want_type, found_type;
473 474 475 476 477 478 479

		if (!want)
			continue;

		want_type  = git_attr_value(want);
		found_type = git_attr_value(strs[i]);

480 481
		if (want_type != found_type)
			error = GIT_ENOTFOUND;
482
		else if (want_type == GIT_ATTR_VALUE_STRING &&
483 484
				strcmp(want, strs[i]) &&
				strcmp(want, "*"))
485 486 487 488
			error = GIT_ENOTFOUND;
	}

	if (error)
Russell Belfer committed
489
		git__free((void *)strs);
490 491 492 493 494 495
	else
		*out = strs;

	return error;
}

496
int git_filter_list_new(
497 498 499
	git_filter_list **out,
	git_repository *repo,
	git_filter_mode_t mode,
500
	uint32_t flags)
501 502 503 504 505
{
	git_filter_source src = { 0 };
	src.repo = repo;
	src.path = NULL;
	src.mode = mode;
506
	src.options.flags = flags;
507 508 509
	return filter_list_new(out, &src);
}

510
int git_filter_list__load(
511 512
	git_filter_list **filters,
	git_repository *repo,
Russell Belfer committed
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 519 520 521
{
	int error = 0;
	git_filter_list *fl = NULL;
	git_filter_source src = { 0 };
	git_filter_entry *fe;
522 523
	size_t idx;
	git_filter_def *fdef;
524

525
	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
526
		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
527
		return -1;
528
	}
529 530 531

	src.repo = repo;
	src.path = path;
532
	src.mode = mode;
533 534

	memcpy(&src.options, &filter_session->options, sizeof(git_filter_options));
535

Russell Belfer committed
536 537
	if (blob)
		git_oid_cpy(&src.oid, git_blob_id(blob));
538

539
	git_vector_foreach(&filter_registry.filters, idx, fdef) {
540
		const char **values = NULL;
541 542 543 544
		void *payload = NULL;

		if (!fdef || !fdef->filter)
			continue;
545

546
		if (fdef->nattrs > 0) {
547
			error = filter_list_check_attributes(
548 549
				&values, repo,
				filter_session, fdef, &src);
550

551 552 553 554 555 556 557
			if (error == GIT_ENOTFOUND) {
				error = 0;
				continue;
			} else if (error < 0)
				break;
		}

558 559 560
		if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
			break;

561
		if (fdef->filter->check)
562
			error = fdef->filter->check(
563
				fdef->filter, &payload, &src, values);
564

Russell Belfer committed
565
		git__free((void *)values);
566

Russell Belfer committed
567
		if (error == GIT_PASSTHROUGH)
568 569 570 571
			error = 0;
		else if (error < 0)
			break;
		else {
572 573
			if (!fl) {
				if ((error = filter_list_new(&fl, &src)) < 0)
574
					break;
575

576
				fl->temp_buf = filter_session->temp_buf;
577
			}
578 579

			fe = git_array_alloc(fl->filters);
580
			GIT_ERROR_CHECK_ALLOC(fe);
581 582 583

			fe->filter = fdef->filter;
			fe->filter_name = fdef->filter_name;
584 585 586 587
			fe->payload = payload;
		}
	}

588 589
	git_rwlock_rdunlock(&filter_registry.lock);

590 591 592 593 594 595 596 597 598 599
	if (error && fl != NULL) {
		git_array_clear(fl->filters);
		git__free(fl);
		fl = NULL;
	}

	*filters = fl;
	return error;
}

600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
int git_filter_list_load_ext(
	git_filter_list **filters,
	git_repository *repo,
	git_blob *blob, /* can be NULL */
	const char *path,
	git_filter_mode_t mode,
	git_filter_options *opts)
{
	git_filter_session filter_session = GIT_FILTER_SESSION_INIT;

	if (opts)
		memcpy(&filter_session.options, opts, sizeof(git_filter_options));

	return git_filter_list__load(
		filters, repo, blob, path, mode, &filter_session);
}

617 618 619 620 621 622
int git_filter_list_load(
	git_filter_list **filters,
	git_repository *repo,
	git_blob *blob, /* can be NULL */
	const char *path,
	git_filter_mode_t mode,
623
	uint32_t flags)
624
{
625
	git_filter_session filter_session = GIT_FILTER_SESSION_INIT;
626

627
	filter_session.options.flags = flags;
628

629 630
	return git_filter_list__load(
		filters, repo, blob, path, mode, &filter_session);
631 632
}

633 634 635 636 637 638 639 640 641 642 643
void git_filter_list_free(git_filter_list *fl)
{
	uint32_t i;

	if (!fl)
		return;

	for (i = 0; i < git_array_size(fl->filters); ++i) {
		git_filter_entry *fe = git_array_get(fl->filters, i);
		if (fe->filter->cleanup)
			fe->filter->cleanup(fe->filter, fe->payload);
644 645
	}

646 647
	git_array_clear(fl->filters);
	git__free(fl);
648 649
}

650 651 652 653 654 655
int git_filter_list_contains(
	git_filter_list *fl,
	const char *name)
{
	size_t i;

656
	GIT_ASSERT_ARG(name);
657 658 659 660 661 662 663 664 665 666 667 668

	if (!fl)
		return 0;

	for (i = 0; i < fl->filters.size; i++) {
		if (strcmp(fl->filters.ptr[i].filter_name, name) == 0)
			return 1;
	}

	return 0;
}

669 670 671 672 673
int git_filter_list_push(
	git_filter_list *fl, git_filter *filter, void *payload)
{
	int error = 0;
	size_t pos;
674
	git_filter_def *fdef = NULL;
675 676
	git_filter_entry *fe;

677 678
	GIT_ASSERT_ARG(fl);
	GIT_ASSERT_ARG(filter);
679

680
	if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
681
		git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
682 683 684
		return -1;
	}

685
	if (git_vector_search2(
686 687 688 689 690 691 692
			&pos, &filter_registry.filters,
			filter_def_filter_key_check, filter) == 0)
		fdef = git_vector_get(&filter_registry.filters, pos);

	git_rwlock_rdunlock(&filter_registry.lock);

	if (fdef == NULL) {
693
		git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter");
694 695 696 697 698 699 700
		return -1;
	}

	if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
		return error;

	fe = git_array_alloc(fl->filters);
701
	GIT_ERROR_CHECK_ALLOC(fe);
702 703 704 705 706 707
	fe->filter  = filter;
	fe->payload = payload;

	return 0;
}

Russell Belfer committed
708 709 710 711 712
size_t git_filter_list_length(const git_filter_list *fl)
{
	return fl ? git_array_size(fl->filters) : 0;
}

713
struct buf_stream {
714
	git_writestream parent;
715
	git_str *target;
716 717 718 719
	bool complete;
};

static int buf_stream_write(
720
	git_writestream *s, const char *buffer, size_t len)
721
{
722
	struct buf_stream *buf_stream = (struct buf_stream *)s;
723 724
	GIT_ASSERT_ARG(buf_stream);
	GIT_ASSERT(buf_stream->complete == 0);
725

726
	return git_str_put(buf_stream->target, buffer, len);
727
}
728

729
static int buf_stream_close(git_writestream *s)
730 731
{
	struct buf_stream *buf_stream = (struct buf_stream *)s;
732
	GIT_ASSERT_ARG(buf_stream);
733

734
	GIT_ASSERT(buf_stream->complete == 0);
735
	buf_stream->complete = 1;
736

737 738
	return 0;
}
739

740
static void buf_stream_free(git_writestream *s)
741 742 743
{
	GIT_UNUSED(s);
}
744

745
static void buf_stream_init(struct buf_stream *writer, git_str *target)
746 747
{
	memset(writer, 0, sizeof(struct buf_stream));
748

749 750 751
	writer->parent.write = buf_stream_write;
	writer->parent.close = buf_stream_close;
	writer->parent.free = buf_stream_free;
752
	writer->target = target;
753

754
	git_str_clear(target);
755
}
756

757
int git_filter_list_apply_to_buffer(
758 759 760 761
	git_buf *out,
	git_filter_list *filters,
	const char *in,
	size_t in_len)
762
{
763 764 765 766 767 768 769 770 771
	GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len);
}

int git_filter_list__apply_to_buffer(
	git_str *out,
	git_filter_list *filters,
	const char *in,
	size_t in_len)
{
772 773 774
	struct buf_stream writer;
	int error;

775
	buf_stream_init(&writer, out);
776

777
	if ((error = git_filter_list_stream_buffer(filters,
778
		in, in_len, &writer.parent)) < 0)
779 780
			return error;

781
	GIT_ASSERT(writer.complete);
782
	return error;
783
}
784

785
int git_filter_list__convert_buf(
786
	git_str *out,
787
	git_filter_list *filters,
788
	git_str *in)
789 790 791 792
{
	int error;

	if (!filters || git_filter_list_length(filters) == 0) {
793 794
		git_str_swap(out, in);
		git_str_dispose(in);
795 796 797
		return 0;
	}

798
	error = git_filter_list__apply_to_buffer(out, filters,
799 800 801
		in->ptr, in->size);

	if (!error)
802
		git_str_dispose(in);
803 804 805 806

	return error;
}

807
int git_filter_list_apply_to_file(
808
	git_buf *out,
809 810 811 812
	git_filter_list *filters,
	git_repository *repo,
	const char *path)
{
813 814 815 816 817 818 819 820 821
	GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path);
}

int git_filter_list__apply_to_file(
	git_str *out,
	git_filter_list *filters,
	git_repository *repo,
	const char *path)
{
822
	struct buf_stream writer;
823 824
	int error;

825
	buf_stream_init(&writer, out);
826

827
	if ((error = git_filter_list_stream_file(
Leo Yang committed
828
		filters, repo, path, &writer.parent)) < 0)
829
			return error;
830

831
	GIT_ASSERT(writer.complete);
832 833 834
	return error;
}

835
static int buf_from_blob(git_str *out, git_blob *blob)
836
{
837
	git_object_size_t rawsize = git_blob_rawsize(blob);
838 839

	if (!git__is_sizet(rawsize)) {
840
		git_error_set(GIT_ERROR_OS, "blob is too large to filter");
841 842 843
		return -1;
	}

844
	git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
845 846 847
	return 0;
}

848
int git_filter_list_apply_to_blob(
849
	git_buf *out,
850 851 852
	git_filter_list *filters,
	git_blob *blob)
{
853 854 855 856 857 858 859 860
	GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob);
}

int git_filter_list__apply_to_blob(
	git_str *out,
	git_filter_list *filters,
	git_blob *blob)
{
861 862
	struct buf_stream writer;
	int error;
863

864
	buf_stream_init(&writer, out);
865

866
	if ((error = git_filter_list_stream_blob(
Leo Yang committed
867
		filters, blob, &writer.parent)) < 0)
868
			return error;
869

870
	GIT_ASSERT(writer.complete);
871
	return error;
872 873
}

874
struct buffered_stream {
875
	git_writestream parent;
876
	git_filter *filter;
877 878
	int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *);
	int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *);
879 880
	const git_filter_source *source;
	void **payload;
881 882 883
	git_str input;
	git_str temp_buf;
	git_str *output;
884
	git_writestream *target;
885 886
};

887
static int buffered_stream_write(
888
	git_writestream *s, const char *buffer, size_t len)
889
{
890 891
	struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
	GIT_ASSERT_ARG(buffered_stream);
892

893
	return git_str_put(&buffered_stream->input, buffer, len);
894 895
}

896
static int buffered_stream_close(git_writestream *s)
897
{
898
	struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
899
	git_str *writebuf;
900
	git_error_state error_state = {0};
901 902
	int error;

903
	GIT_ASSERT_ARG(buffered_stream);
904

905 906 907 908 909 910
	error = buffered_stream->write_fn(
		buffered_stream->filter,
		buffered_stream->payload,
		buffered_stream->output,
		&buffered_stream->input,
		buffered_stream->source);
911 912

	if (error == GIT_PASSTHROUGH) {
913
		writebuf = &buffered_stream->input;
914
	} else if (error == 0) {
915
		writebuf = buffered_stream->output;
916
	} else {
917 918
		/* close stream before erroring out taking care
		 * to preserve the original error */
919
		git_error_state_capture(&error_state, error);
920
		buffered_stream->target->close(buffered_stream->target);
921
		git_error_state_restore(&error_state);
922 923 924
		return error;
	}

925 926 927
	if ((error = buffered_stream->target->write(
			buffered_stream->target, writebuf->ptr, writebuf->size)) == 0)
		error = buffered_stream->target->close(buffered_stream->target);
928 929 930 931

	return error;
}

932
static void buffered_stream_free(git_writestream *s)
933
{
934
	struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
935

936
	if (buffered_stream) {
937 938
		git_str_dispose(&buffered_stream->input);
		git_str_dispose(&buffered_stream->temp_buf);
939
		git__free(buffered_stream);
940
	}
941 942
}

943
int git_filter_buffered_stream_new(
944
	git_writestream **out,
945
	git_filter *filter,
946 947
	int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *),
	git_str *temp_buf,
948 949
	void **payload,
	const git_filter_source *source,
950
	git_writestream *target)
951
{
952 953
	struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream));
	GIT_ERROR_CHECK_ALLOC(buffered_stream);
954

955 956 957 958 959 960 961 962 963
	buffered_stream->parent.write = buffered_stream_write;
	buffered_stream->parent.close = buffered_stream_close;
	buffered_stream->parent.free = buffered_stream_free;
	buffered_stream->filter = filter;
	buffered_stream->write_fn = write_fn;
	buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf;
	buffered_stream->payload = payload;
	buffered_stream->source = source;
	buffered_stream->target = target;
964

965
	if (temp_buf)
966
		git_str_clear(temp_buf);
967

968
	*out = (git_writestream *)buffered_stream;
969 970 971
	return 0;
}

972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
#ifndef GIT_DEPRECATE_HARD
static int buffered_legacy_stream_new(
	git_writestream **out,
	git_filter *filter,
	int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *),
	git_str *temp_buf,
	void **payload,
	const git_filter_source *source,
	git_writestream *target)
{
	struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream));
	GIT_ERROR_CHECK_ALLOC(buffered_stream);

	buffered_stream->parent.write = buffered_stream_write;
	buffered_stream->parent.close = buffered_stream_close;
	buffered_stream->parent.free = buffered_stream_free;
	buffered_stream->filter = filter;
	buffered_stream->legacy_write_fn = legacy_write_fn;
	buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf;
	buffered_stream->payload = payload;
	buffered_stream->source = source;
	buffered_stream->target = target;

	if (temp_buf)
		git_str_clear(temp_buf);

	*out = (git_writestream *)buffered_stream;
	return 0;
}
#endif

1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
static int setup_stream(
	git_writestream **out,
	git_filter_entry *fe,
	git_filter_list *filters,
	git_writestream *last_stream)
{
#ifndef GIT_DEPRECATE_HARD
	GIT_ASSERT(fe->filter->stream || fe->filter->apply);

	/*
	 * If necessary, create a stream that proxies the traditional
	 * application.
	 */
	if (!fe->filter->stream) {
		/* Create a stream that proxies the one-shot apply */
1018
		return buffered_legacy_stream_new(out,
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
			fe->filter, fe->filter->apply, filters->temp_buf,
			&fe->payload, &filters->source, last_stream);
	}
#endif

	GIT_ASSERT(fe->filter->stream);
	return fe->filter->stream(out, fe->filter,
		&fe->payload, &filters->source, last_stream);
}

1029
static int stream_list_init(
1030
	git_writestream **out,
1031 1032
	git_vector *streams,
	git_filter_list *filters,
1033
	git_writestream *target)
1034
{
1035
	git_writestream *last_stream = target;
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
	size_t i;
	int error = 0;

	*out = NULL;

	if (!filters) {
		*out = target;
		return 0;
	}

	/* Create filters last to first to get the chaining direction */
	for (i = 0; i < git_array_size(filters->filters); ++i) {
		size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ?
			git_array_size(filters->filters) - 1 - i : i;
1050

1051
		git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
1052
		git_writestream *filter_stream;
1053

1054
		error = setup_stream(&filter_stream, fe, filters, last_stream);
1055

1056
		if (error < 0)
1057
			goto out;
1058 1059

		git_vector_insert(streams, filter_stream);
1060
		last_stream = filter_stream;
1061 1062
	}

1063 1064 1065 1066 1067 1068 1069
out:
	if (error)
		last_stream->close(last_stream);
	else
		*out = last_stream;

	return error;
1070 1071
}

1072
static void filter_streams_free(git_vector *streams)
1073
{
1074
	git_writestream *stream;
1075 1076 1077 1078
	size_t i;

	git_vector_foreach(streams, i, stream)
		stream->free(stream);
1079
	git_vector_free(streams);
1080 1081 1082 1083 1084 1085
}

int git_filter_list_stream_file(
	git_filter_list *filters,
	git_repository *repo,
	const char *path,
1086
	git_writestream *target)
1087
{
1088
	char buf[GIT_BUFSIZE_FILTERIO];
1089
	git_str abspath = GIT_STR_INIT;
1090 1091
	const char *base = repo ? git_repository_workdir(repo) : NULL;
	git_vector filter_streams = GIT_VECTOR_INIT;
1092
	git_writestream *stream_start;
1093
	ssize_t readlen;
1094
	int fd = -1, error, initialized = 0;
1095 1096 1097

	if ((error = stream_list_init(
			&stream_start, &filter_streams, filters, target)) < 0 ||
1098
	    (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 ||
1099
	    (error = git_path_validate_str_length(repo, &abspath)) < 0)
1100
		goto done;
1101

1102
	initialized = 1;
1103

1104
	if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
1105 1106 1107 1108
		error = fd;
		goto done;
	}

1109
	while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
1110
		if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
1111 1112 1113
			goto done;
	}

1114
	if (readlen < 0)
Edward Thomson committed
1115
		error = -1;
1116 1117

done:
1118 1119 1120
	if (initialized)
		error |= stream_start->close(stream_start);

1121 1122
	if (fd >= 0)
		p_close(fd);
1123
	filter_streams_free(&filter_streams);
1124
	git_str_dispose(&abspath);
1125 1126 1127
	return error;
}

1128
int git_filter_list_stream_buffer(
1129
	git_filter_list *filters,
1130 1131
	const char *buffer,
	size_t len,
1132
	git_writestream *target)
1133 1134
{
	git_vector filter_streams = GIT_VECTOR_INIT;
1135
	git_writestream *stream_start;
1136
	int error, initialized = 0;
1137

1138 1139
	if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
		goto out;
1140
	initialized = 1;
1141

1142
	if ((error = stream_start->write(stream_start, buffer, len)) < 0)
1143
		goto out;
1144 1145

out:
1146 1147 1148
	if (initialized)
		error |= stream_start->close(stream_start);

1149
	filter_streams_free(&filter_streams);
1150
	return error;
1151 1152 1153 1154 1155
}

int git_filter_list_stream_blob(
	git_filter_list *filters,
	git_blob *blob,
1156
	git_writestream *target)
1157
{
1158
	git_str in = GIT_STR_INIT;
1159 1160 1161

	if (buf_from_blob(&in, blob) < 0)
		return -1;
1162

Russell Belfer committed
1163 1164 1165
	if (filters)
		git_oid_cpy(&filters->source.oid, git_blob_id(blob));

1166
	return git_filter_list_stream_buffer(filters, in.ptr, in.size, target);
1167
}
1168 1169 1170 1171 1172 1173

int git_filter_init(git_filter *filter, unsigned int version)
{
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT);
	return 0;
}
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184

#ifndef GIT_DEPRECATE_HARD

int git_filter_list_stream_data(
	git_filter_list *filters,
	git_buf *data,
	git_writestream *target)
{
	return git_filter_list_stream_buffer(filters, data->ptr, data->size, target);
}

1185 1186 1187 1188 1189 1190
int git_filter_list_apply_to_data(
	git_buf *tgt, git_filter_list *filters, git_buf *src)
{
	return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size);
}

1191
#endif