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

8
#include "common.h"
9

10
#include "diff.h"
11
#include "diff_file.h"
12
#include "patch_generate.h"
13
#include "fileops.h"
14 15 16
#include "zstream.h"
#include "blob.h"
#include "delta.h"
17
#include "git2/sys/diff.h"
18 19

typedef struct {
Russell Belfer committed
20
	git_diff_format_t format;
21
	git_diff_line_cb print_cb;
22
	void *payload;
23

24
	git_buf *buf;
25 26 27 28
	git_diff_line line;

	const char *old_prefix;
	const char *new_prefix;
Russell Belfer committed
29
	uint32_t flags;
30
	int id_strlen;
31 32

	int (*strcomp)(const char *, const char *);
33 34
} diff_print_info;

35
static int diff_print_info_init__common(
36
	diff_print_info *pi,
Russell Belfer committed
37
	git_buf *out,
38
	git_repository *repo,
Russell Belfer committed
39 40 41
	git_diff_format_t format,
	git_diff_line_cb cb,
	void *payload)
42
{
43
	pi->format = format;
44
	pi->print_cb = cb;
45 46 47
	pi->payload = payload;
	pi->buf = out;

48
	if (!pi->id_strlen) {
49
		if (!repo)
50 51
			pi->id_strlen = GIT_ABBREV_DEFAULT;
		else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0)
52 53
			return -1;
	}
54

55 56
	if (pi->id_strlen > GIT_OID_HEXSZ)
		pi->id_strlen = GIT_OID_HEXSZ;
57

58 59 60
	memset(&pi->line, 0, sizeof(pi->line));
	pi->line.old_lineno = -1;
	pi->line.new_lineno = -1;
61
	pi->line.num_lines = 1;
62

63 64 65
	return 0;
}

66 67 68 69 70 71 72 73 74 75 76 77 78 79
static int diff_print_info_init_fromdiff(
	diff_print_info *pi,
	git_buf *out,
	git_diff *diff,
	git_diff_format_t format,
	git_diff_line_cb cb,
	void *payload)
{
	git_repository *repo = diff ? diff->repo : NULL;

	memset(pi, 0, sizeof(diff_print_info));

	if (diff) {
		pi->flags = diff->opts.flags;
80
		pi->id_strlen = diff->opts.id_abbrev;
81 82 83 84
		pi->old_prefix = diff->opts.old_prefix;
		pi->new_prefix = diff->opts.new_prefix;

		pi->strcomp = diff->strcomp;
85 86 87 88 89 90 91 92 93 94 95 96 97
	}

	return diff_print_info_init__common(pi, out, repo, format, cb, payload);
}

static int diff_print_info_init_frompatch(
	diff_print_info *pi,
	git_buf *out,
	git_patch *patch,
	git_diff_format_t format,
	git_diff_line_cb cb,
	void *payload)
{
98 99
	assert(patch);

100 101 102
	memset(pi, 0, sizeof(diff_print_info));

	pi->flags = patch->diff_opts.flags;
103
	pi->id_strlen = patch->diff_opts.id_abbrev;
104 105
	pi->old_prefix = patch->diff_opts.old_prefix;
	pi->new_prefix = patch->diff_opts.new_prefix;
106

107
	return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
108 109
}

110
static char diff_pick_suffix(int mode)
111 112 113
{
	if (S_ISDIR(mode))
		return '/';
114
	else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
115 116 117 118 119 120 121 122 123 124 125
		/* in git, modes are very regular, so we must have 0100755 mode */
		return '*';
	else
		return ' ';
}

char git_diff_status_char(git_delta_t status)
{
	char code;

	switch (status) {
126 127 128 129 130 131 132 133 134
	case GIT_DELTA_ADDED:      code = 'A'; break;
	case GIT_DELTA_DELETED:    code = 'D'; break;
	case GIT_DELTA_MODIFIED:   code = 'M'; break;
	case GIT_DELTA_RENAMED:    code = 'R'; break;
	case GIT_DELTA_COPIED:     code = 'C'; break;
	case GIT_DELTA_IGNORED:    code = 'I'; break;
	case GIT_DELTA_UNTRACKED:  code = '?'; break;
	case GIT_DELTA_UNREADABLE: code = 'X'; break;
	default:                   code = ' '; break;
135 136 137 138 139
	}

	return code;
}

Russell Belfer committed
140 141 142 143 144 145 146 147 148 149 150 151 152
static int diff_print_one_name_only(
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
	git_buf *out = pi->buf;

	GIT_UNUSED(progress);

	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
		delta->status == GIT_DELTA_UNMODIFIED)
		return 0;

	git_buf_clear(out);
153 154 155 156
	git_buf_puts(out, delta->new_file.path);
	git_buf_putc(out, '\n');
	if (git_buf_oom(out))
		return -1;
Russell Belfer committed
157

158 159 160 161
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

162
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
Russell Belfer committed
163 164 165
}

static int diff_print_one_name_status(
166 167 168
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
169
	git_buf *out = pi->buf;
170
	char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
171 172
	int(*strcomp)(const char *, const char *) = pi->strcomp ?
		pi->strcomp : git__strcmp;
173 174 175

	GIT_UNUSED(progress);

Russell Belfer committed
176
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
177 178
		return 0;

179 180
	old_suffix = diff_pick_suffix(delta->old_file.mode);
	new_suffix = diff_pick_suffix(delta->new_file.mode);
181

182
	git_buf_clear(out);
183 184

	if (delta->old_file.path != delta->new_file.path &&
185
		strcomp(delta->old_file.path,delta->new_file.path) != 0)
186
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
187 188 189
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
	else if (delta->old_file.mode != delta->new_file.mode &&
		delta->old_file.mode != 0 && delta->new_file.mode != 0)
190 191
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
192
	else if (old_suffix != ' ')
193
		git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
194
	else
195 196
		git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
	if (git_buf_oom(out))
197
		return -1;
198

199 200 201 202
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

203
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
204 205
}

206
static int diff_print_one_raw(
207 208 209
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
210
	git_buf *out = pi->buf;
211
	int id_abbrev;
212 213 214 215 216
	char code = git_diff_status_char(delta->status);
	char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];

	GIT_UNUSED(progress);

Russell Belfer committed
217
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
218 219
		return 0;

220
	git_buf_clear(out);
221

222 223 224
	id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
		delta->new_file.id_abbrev;

225
	if (pi->id_strlen > id_abbrev) {
226
		giterr_set(GITERR_PATCH,
227
			"the patch input contains %d id characters (cannot print %d)",
228
			id_abbrev, pi->id_strlen);
229 230 231
		return -1;
	}

232 233
	git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
	git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
234 235

	git_buf_printf(
236
		out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
237
			":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
238 239 240
		delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);

	if (delta->similarity > 0)
241
		git_buf_printf(out, "%03u", delta->similarity);
242

243
	if (delta->old_file.path != delta->new_file.path)
244
		git_buf_printf(
245
			out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
246 247
	else
		git_buf_printf(
248
			out, "\t%s\n", delta->old_file.path ?
249 250
			delta->old_file.path : delta->new_file.path);

251
	if (git_buf_oom(out))
252
		return -1;
253

254 255 256 257
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

258
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
259 260
}

261 262 263 264 265 266 267 268 269
static int diff_print_modes(
	git_buf *out, const git_diff_delta *delta)
{
	git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
	git_buf_printf(out, "new mode %o\n", delta->new_file.mode);

	return git_buf_oom(out) ? -1 : 0;
}

270
static int diff_print_oid_range(
271
	git_buf *out, const git_diff_delta *delta, int id_strlen)
272 273 274
{
	char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];

275
	if (delta->old_file.mode &&
276
			id_strlen > delta->old_file.id_abbrev) {
277
		giterr_set(GITERR_PATCH,
278
			"the patch input contains %d id characters (cannot print %d)",
279
			delta->old_file.id_abbrev, id_strlen);
280 281 282 283
		return -1;
	}

	if ((delta->new_file.mode &&
284
			id_strlen > delta->new_file.id_abbrev)) {
285
		giterr_set(GITERR_PATCH,
286
			"the patch input contains %d id characters (cannot print %d)",
287
			delta->new_file.id_abbrev, id_strlen);
288 289 290
		return -1;
	}

291 292
	git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
	git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
293 294

	if (delta->old_file.mode == delta->new_file.mode) {
295
		git_buf_printf(out, "index %s..%s %o\n",
296 297
			start_oid, end_oid, delta->old_file.mode);
	} else {
298
		if (delta->old_file.mode == 0)
299
			git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
300
		else if (delta->new_file.mode == 0)
301
			git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
302 303 304
		else
			diff_print_modes(out, delta);

305
		git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
306 307
	}

308
	return git_buf_oom(out) ? -1 : 0;
309 310
}

311 312 313 314 315 316 317 318 319
static int diff_delta_format_path(
	git_buf *out, const char *prefix, const char *filename)
{
	if (git_buf_joinpath(out, prefix, filename) < 0)
		return -1;

	return git_buf_quote(out);
}

320
static int diff_delta_format_with_paths(
321 322
	git_buf *out,
	const git_diff_delta *delta,
323 324 325
	const char *template,
	const char *oldpath,
	const char *newpath)
326
{
327
	if (git_oid_iszero(&delta->old_file.id))
328
		oldpath = "/dev/null";
329 330

	if (git_oid_iszero(&delta->new_file.id))
331 332
		newpath = "/dev/null";

333
	return git_buf_printf(out, template, oldpath, newpath);
334 335
}

336
int diff_delta_format_similarity_header(
337 338 339
	git_buf *out,
	const git_diff_delta *delta)
{
340
	git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
341
	const char *type;
342 343
	int error = 0;

344 345
	if (delta->similarity > 100) {
		giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity);
346 347
		error = -1;
		goto done;
348 349
	}

350 351 352 353 354 355 356
	if (delta->status == GIT_DELTA_RENAMED)
		type = "rename";
	else if (delta->status == GIT_DELTA_COPIED)
		type = "copy";
	else
		abort();

357 358 359 360 361 362
	if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
		(error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
		(error = git_buf_quote(&old_path)) < 0 ||
		(error = git_buf_quote(&new_path)) < 0)
		goto done;

363 364
	git_buf_printf(out,
		"similarity index %d%%\n"
365 366
		"%s from %s\n"
		"%s to %s\n",
367
		delta->similarity,
368 369
		type, old_path.ptr,
		type, new_path.ptr);
370

371 372 373 374 375 376 377 378
	if (git_buf_oom(out))
		error = -1;

done:
	git_buf_free(&old_path);
	git_buf_free(&new_path);

	return error;
379 380
}

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
static bool delta_is_unchanged(const git_diff_delta *delta)
{
	if (git_oid_iszero(&delta->old_file.id) &&
		git_oid_iszero(&delta->new_file.id))
		return true;

	if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
		delta->new_file.mode == GIT_FILEMODE_COMMIT)
		return false;

	if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
		return true;

	return false;
}

397 398 399 400 401
int git_diff_delta__format_file_header(
	git_buf *out,
	const git_diff_delta *delta,
	const char *oldpfx,
	const char *newpfx,
402
	int id_strlen)
403
{
404
	git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
405
	bool unchanged = delta_is_unchanged(delta);
406
	int error = 0;
407

408 409 410 411
	if (!oldpfx)
		oldpfx = DIFF_OLD_PREFIX_DEFAULT;
	if (!newpfx)
		newpfx = DIFF_NEW_PREFIX_DEFAULT;
412 413
	if (!id_strlen)
		id_strlen = GIT_ABBREV_DEFAULT;
414

415 416 417 418 419 420
	if ((error = diff_delta_format_path(
			&old_path, oldpfx, delta->old_file.path)) < 0 ||
		(error = diff_delta_format_path(
			&new_path, newpfx, delta->new_file.path)) < 0)
		goto done;

421 422
	git_buf_clear(out);

423 424
	git_buf_printf(out, "diff --git %s %s\n",
		old_path.ptr, new_path.ptr);
425

426 427 428
	if (delta->status == GIT_DELTA_RENAMED ||
		(delta->status == GIT_DELTA_COPIED && unchanged)) {
		if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
429 430
			goto done;
	}
431

432
	if (!unchanged) {
433
		if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0)
434
			goto done;
435

436
		if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
437 438
			diff_delta_format_with_paths(out, delta,
				"--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
439
	}
440

441 442 443
	if (unchanged && delta->old_file.mode != delta->new_file.mode)
		diff_print_modes(out, delta);

444 445 446 447 448 449 450 451
	if (git_buf_oom(out))
		error = -1;

done:
	git_buf_free(&old_path);
	git_buf_free(&new_path);

	return error;
452
}
453

454 455 456 457 458 459
static int format_binary(
	diff_print_info *pi,
	git_diff_binary_t type,
	const char *data,
	size_t datalen,
	size_t inflatedlen)
460
{
461 462 463
	const char *typename = type == GIT_DIFF_BINARY_DELTA ?
		"delta" : "literal";
	const char *scan, *end;
464

465
	git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen);
466 467
	pi->line.num_lines++;

468
	for (scan = data, end = data + datalen; scan < end; ) {
469 470 471
		size_t chunk_len = end - scan;
		if (chunk_len > 52)
			chunk_len = 52;
472 473

		if (chunk_len <= 26)
474
			git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
475
		else
476
			git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
477

478
		git_buf_encode_base85(pi->buf, scan, chunk_len);
479 480
		git_buf_putc(pi->buf, '\n');

481 482
		if (git_buf_oom(pi->buf))
			return -1;
483

484
		scan += chunk_len;
485 486
		pi->line.num_lines++;
	}
Guille -bisho- committed
487
	git_buf_putc(pi->buf, '\n');
488

489 490
	return 0;
}
491

492 493
static int diff_print_patch_file_binary_noshow(
	diff_print_info *pi, git_diff_delta *delta,
494
	const char *old_pfx, const char *new_pfx)
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
{
	git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
	int error;

	if ((error = diff_delta_format_path(
			&old_path, old_pfx, delta->old_file.path)) < 0 ||
		(error = diff_delta_format_path(
			&new_path, new_pfx, delta->new_file.path)) < 0)
		goto done;

	pi->line.num_lines = 1;
	error = diff_delta_format_with_paths(
		pi->buf, delta, "Binary files %s and %s differ\n",
		old_path.ptr, new_path.ptr);

done:
	git_buf_free(&old_path);
	git_buf_free(&new_path);

	return error;
}

517
static int diff_print_patch_file_binary(
518 519 520
	diff_print_info *pi, git_diff_delta *delta,
	const char *old_pfx, const char *new_pfx,
	const git_diff_binary *binary)
521
{
522
	size_t pre_binary_size;
523
	int error;
524

525 526 527
	if (delta->status == GIT_DELTA_UNMODIFIED)
		return 0;

528
	if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data)
529
		return diff_print_patch_file_binary_noshow(
530
			pi, delta, old_pfx, new_pfx);
531

532
	pre_binary_size = pi->buf->size;
533 534 535
	git_buf_printf(pi->buf, "GIT binary patch\n");
	pi->line.num_lines++;

536 537 538 539 540
	if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data,
		binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
		(error = format_binary(pi, binary->old_file.type, binary->old_file.data,
			binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {

541 542 543
		if (error == GIT_EBUFS) {
			giterr_clear();
			git_buf_truncate(pi->buf, pre_binary_size);
544 545

			return diff_print_patch_file_binary_noshow(
546
				pi, delta, old_pfx, new_pfx);
547 548
		}
	}
549 550 551 552 553

	pi->line.num_lines++;
	return error;
}

554 555 556
static int diff_print_patch_file(
	const git_diff_delta *delta, float progress, void *data)
{
557
	int error;
558
	diff_print_info *pi = data;
559
	const char *oldpfx =
560
		pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
561
	const char *newpfx =
562
		pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
563

564 565
	bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
		(pi->flags & GIT_DIFF_FORCE_BINARY);
566
	bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
567 568 569 570 571
	int id_strlen = pi->id_strlen;

	if (binary && show_binary)
		id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev :
			delta->new_file.id_abbrev;
572

573
	GIT_UNUSED(progress);
574

575 576 577
	if (S_ISDIR(delta->new_file.mode) ||
		delta->status == GIT_DELTA_UNMODIFIED ||
		delta->status == GIT_DELTA_IGNORED ||
578
		delta->status == GIT_DELTA_UNREADABLE ||
579
		(delta->status == GIT_DELTA_UNTRACKED &&
Russell Belfer committed
580
		 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
581 582
		return 0;

583
	if ((error = git_diff_delta__format_file_header(
584
			pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0)
585
		return error;
586

587 588 589 590
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(pi->buf);
	pi->line.content_len = git_buf_len(pi->buf);

591 592
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
593

594 595 596 597 598 599 600
static int diff_print_patch_binary(
	const git_diff_delta *delta,
	const git_diff_binary *binary,
	void *data)
{
	diff_print_info *pi = data;
	const char *old_pfx =
601
		pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
602
	const char *new_pfx =
603
		pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
604
	int error;
605 606 607

	git_buf_clear(pi->buf);

608 609
	if ((error = diff_print_patch_file_binary(
		pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
610
		return error;
611

612 613
	pi->line.origin = GIT_DIFF_LINE_BINARY;
	pi->line.content = git_buf_cstr(pi->buf);
614 615
	pi->line.content_len = git_buf_len(pi->buf);

616
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
617 618
}

619
static int diff_print_patch_hunk(
620
	const git_diff_delta *d,
621
	const git_diff_hunk *h,
622 623 624 625 626 627 628
	void *data)
{
	diff_print_info *pi = data;

	if (S_ISDIR(d->new_file.mode))
		return 0;

629 630 631
	pi->line.origin      = GIT_DIFF_LINE_HUNK_HDR;
	pi->line.content     = h->header;
	pi->line.content_len = h->header_len;
632

633
	return pi->print_cb(d, h, &pi->line, pi->payload);
634 635
}

636
static int diff_print_patch_line(
637
	const git_diff_delta *delta,
638 639
	const git_diff_hunk *hunk,
	const git_diff_line *line,
640 641 642 643 644 645 646
	void *data)
{
	diff_print_info *pi = data;

	if (S_ISDIR(delta->new_file.mode))
		return 0;

647
	return pi->print_cb(delta, hunk, line, pi->payload);
648 649
}

Russell Belfer committed
650 651
/* print a git_diff to an output callback */
int git_diff_print(
652
	git_diff *diff,
Russell Belfer committed
653
	git_diff_format_t format,
654
	git_diff_line_cb print_cb,
655 656 657 658 659
	void *payload)
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;
Russell Belfer committed
660
	git_diff_file_cb print_file = NULL;
661
	git_diff_binary_cb print_binary = NULL;
Russell Belfer committed
662 663 664 665 666 667
	git_diff_hunk_cb print_hunk = NULL;
	git_diff_line_cb print_line = NULL;

	switch (format) {
	case GIT_DIFF_FORMAT_PATCH:
		print_file = diff_print_patch_file;
668
		print_binary = diff_print_patch_binary;
Russell Belfer committed
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
		print_hunk = diff_print_patch_hunk;
		print_line = diff_print_patch_line;
		break;
	case GIT_DIFF_FORMAT_PATCH_HEADER:
		print_file = diff_print_patch_file;
		break;
	case GIT_DIFF_FORMAT_RAW:
		print_file = diff_print_one_raw;
		break;
	case GIT_DIFF_FORMAT_NAME_ONLY:
		print_file = diff_print_one_name_only;
		break;
	case GIT_DIFF_FORMAT_NAME_STATUS:
		print_file = diff_print_one_name_status;
		break;
	default:
685
		giterr_set(GITERR_INVALID, "unknown diff output format (%d)", format);
Russell Belfer committed
686 687
		return -1;
	}
688

689 690
	if (!(error = diff_print_info_init_fromdiff(
			&pi, &buf, diff, format, print_cb, payload))) {
691
		error = git_diff_foreach(
692
			diff, print_file, print_binary, print_hunk, print_line, &pi);
693

694
		if (error) /* make sure error message is set */
695
			giterr_set_after_callback_function(error, "git_diff_print");
696 697
	}

698 699 700 701 702
	git_buf_free(&buf);

	return error;
}

703
int git_diff_print_callback__to_buf(
704
	const git_diff_delta *delta,
705 706
	const git_diff_hunk *hunk,
	const git_diff_line *line,
707 708 709
	void *payload)
{
	git_buf *output = payload;
710 711
	GIT_UNUSED(delta); GIT_UNUSED(hunk);

712
	if (!output) {
713
		giterr_set(GITERR_INVALID, "buffer pointer must be provided");
714 715 716
		return -1;
	}

717 718 719 720 721 722
	if (line->origin == GIT_DIFF_LINE_ADDITION ||
		line->origin == GIT_DIFF_LINE_DELETION ||
		line->origin == GIT_DIFF_LINE_CONTEXT)
		git_buf_putc(output, line->origin);

	return git_buf_put(output, line->content, line->content_len);
723 724
}

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
int git_diff_print_callback__to_file_handle(
	const git_diff_delta *delta,
	const git_diff_hunk *hunk,
	const git_diff_line *line,
	void *payload)
{
	FILE *fp = payload ? payload : stdout;

	GIT_UNUSED(delta); GIT_UNUSED(hunk);

	if (line->origin == GIT_DIFF_LINE_CONTEXT ||
		line->origin == GIT_DIFF_LINE_ADDITION ||
		line->origin == GIT_DIFF_LINE_DELETION)
		fputc(line->origin, fp);
	fwrite(line->content, 1, line->content_len, fp);
	return 0;
}

743 744 745 746 747 748 749 750 751
/* print a git_diff to a git_buf */
int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
{
	assert(out && diff);
	git_buf_sanitize(out);
	return git_diff_print(
		diff, format, git_diff_print_callback__to_buf, out);
}

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
/* print a git_patch to an output callback */
int git_patch_print(
	git_patch *patch,
	git_diff_line_cb print_cb,
	void *payload)
{
	int error;
	git_buf temp = GIT_BUF_INIT;
	diff_print_info pi;

	assert(patch && print_cb);

	if (!(error = diff_print_info_init_frompatch(
		&pi, &temp, patch,
		GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
	{
		error = git_patch__invoke_callbacks(
			patch,
			diff_print_patch_file, diff_print_patch_binary,
			diff_print_patch_hunk, diff_print_patch_line,
			&pi);

		if (error) /* make sure error message is set */
			giterr_set_after_callback_function(error, "git_patch_print");
	}

	git_buf_free(&temp);

	return error;
}

783
/* print a git_patch to a git_buf */
784
int git_patch_to_buf(git_buf *out, git_patch *patch)
785
{
786 787
	assert(out && patch);
	git_buf_sanitize(out);
788
	return git_patch_print(patch, git_diff_print_callback__to_buf, out);
789
}