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
	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;
133
	case GIT_DELTA_TYPECHANGE: code = 'T'; break;
134 135
	case GIT_DELTA_UNREADABLE: code = 'X'; break;
	default:                   code = ' '; break;
136 137 138 139 140
	}

	return code;
}

Russell Belfer committed
141 142 143 144 145 146 147 148 149 150 151 152 153
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);
154 155 156 157
	git_buf_puts(out, delta->new_file.path);
	git_buf_putc(out, '\n');
	if (git_buf_oom(out))
		return -1;
Russell Belfer committed
158

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

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

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

	GIT_UNUSED(progress);

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

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

183
	git_buf_clear(out);
184 185

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

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

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

207
static int diff_print_one_raw(
208 209 210
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
211
	git_buf *out = pi->buf;
212
	int id_abbrev;
213 214 215 216 217
	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
218
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
219 220
		return 0;

221
	git_buf_clear(out);
222

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

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

233 234
	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);
235 236

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

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

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

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

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

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

262 263 264 265 266 267 268 269 270
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;
}

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

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

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

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

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

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

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

312 313 314 315 316 317 318 319 320
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);
}

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

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

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

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

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

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

358 359 360 361 362 363
	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;

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

372 373 374 375
	if (git_buf_oom(out))
		error = -1;

done:
376 377
	git_buf_dispose(&old_path);
	git_buf_dispose(&new_path);
378 379

	return error;
380 381
}

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
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;
}

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

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

416 417 418 419 420 421
	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;

422 423
	git_buf_clear(out);

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

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

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

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

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

445 446 447 448
	if (git_buf_oom(out))
		error = -1;

done:
449 450
	git_buf_dispose(&old_path);
	git_buf_dispose(&new_path);
451 452

	return error;
453
}
454

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

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

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

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

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

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

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

490 491
	return 0;
}
492

493 494
static int diff_print_patch_file_binary_noshow(
	diff_print_info *pi, git_diff_delta *delta,
495
	const char *old_pfx, const char *new_pfx)
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
{
	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:
512 513
	git_buf_dispose(&old_path);
	git_buf_dispose(&new_path);
514 515 516 517

	return error;
}

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

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

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

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

537 538 539 540 541
	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) {

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

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

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

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

565 566
	bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
		(pi->flags & GIT_DIFF_FORCE_BINARY);
567
	bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
568 569 570 571 572
	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;
573

574
	GIT_UNUSED(progress);
575

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

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

588 589 590 591
	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);

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

595 596 597 598 599 600 601
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 =
602
		pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
603
	const char *new_pfx =
604
		pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
605
	int error;
606 607 608

	git_buf_clear(pi->buf);

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

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

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

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

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

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

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

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

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

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

Russell Belfer committed
651 652
/* print a git_diff to an output callback */
int git_diff_print(
653
	git_diff *diff,
Russell Belfer committed
654
	git_diff_format_t format,
655
	git_diff_line_cb print_cb,
656 657 658 659 660
	void *payload)
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;
Russell Belfer committed
661
	git_diff_file_cb print_file = NULL;
662
	git_diff_binary_cb print_binary = NULL;
Russell Belfer committed
663 664 665 666 667 668
	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;
669
		print_binary = diff_print_patch_binary;
Russell Belfer committed
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
		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:
686
		giterr_set(GITERR_INVALID, "unknown diff output format (%d)", format);
Russell Belfer committed
687 688
		return -1;
	}
689

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

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

699
	git_buf_dispose(&buf);
700 701 702 703

	return error;
}

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

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

718 719 720 721 722 723
	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);
724 725
}

726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
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;
}

744 745 746 747 748 749 750 751 752
/* 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);
}

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
/* 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");
	}

779
	git_buf_dispose(&temp);
780 781 782 783

	return error;
}

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