diff_print.c 15.9 KB
Newer Older
1 2 3 4 5 6 7 8
/*
 * 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.
 */
#include "common.h"
#include "diff.h"
9
#include "diff_patch.h"
10
#include "fileops.h"
11 12 13
#include "zstream.h"
#include "blob.h"
#include "delta.h"
14
#include "git2/sys/diff.h"
15 16

typedef struct {
17
	git_diff *diff;
Russell Belfer committed
18
	git_diff_format_t format;
19
	git_diff_line_cb print_cb;
20 21
	void *payload;
	git_buf *buf;
Russell Belfer committed
22
	uint32_t flags;
23
	int oid_strlen;
24
	git_diff_line line;
25 26 27 28
} diff_print_info;

static int diff_print_info_init(
	diff_print_info *pi,
Russell Belfer committed
29 30 31 32 33
	git_buf *out,
	git_diff *diff,
	git_diff_format_t format,
	git_diff_line_cb cb,
	void *payload)
34 35
{
	pi->diff     = diff;
Russell Belfer committed
36
	pi->format   = format;
37 38 39 40
	pi->print_cb = cb;
	pi->payload  = payload;
	pi->buf      = out;

Russell Belfer committed
41 42
	if (diff)
		pi->flags = diff->opts.flags;
43 44
	else
		pi->flags = 0;
Russell Belfer committed
45

46 47
	if (diff && diff->opts.id_abbrev != 0)
		pi->oid_strlen = diff->opts.id_abbrev;
Russell Belfer committed
48
	else if (!diff || !diff->repo)
49 50 51
		pi->oid_strlen = GIT_ABBREV_DEFAULT;
	else if (git_repository__cvar(
		&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
52 53 54 55 56 57 58 59 60
		return -1;

	pi->oid_strlen += 1; /* for NUL byte */

	if (pi->oid_strlen < 2)
		pi->oid_strlen = 2;
	else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
		pi->oid_strlen = GIT_OID_HEXSZ + 1;

61 62 63 64 65
	memset(&pi->line, 0, sizeof(pi->line));
	pi->line.old_lineno = -1;
	pi->line.new_lineno = -1;
	pi->line.num_lines  = 1;

66 67 68
	return 0;
}

69
static char diff_pick_suffix(int mode)
70 71 72
{
	if (S_ISDIR(mode))
		return '/';
73
	else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
74 75 76 77 78 79 80 81 82 83 84
		/* 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) {
85 86 87 88 89 90 91 92 93
	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;
94 95 96 97 98
	}

	return code;
}

Russell Belfer committed
99 100 101 102 103 104 105 106 107 108 109 110 111
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);
112 113 114 115
	git_buf_puts(out, delta->new_file.path);
	git_buf_putc(out, '\n');
	if (git_buf_oom(out))
		return -1;
Russell Belfer committed
116

117 118 119 120
	pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
	pi->line.content     = git_buf_cstr(out);
	pi->line.content_len = git_buf_len(out);

121
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
Russell Belfer committed
122 123 124
}

static int diff_print_one_name_status(
125 126 127
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
128
	git_buf *out = pi->buf;
129
	char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
130 131
	int (*strcomp)(const char *, const char *) =
		pi->diff ? pi->diff->strcomp : git__strcmp;
132 133 134

	GIT_UNUSED(progress);

Russell Belfer committed
135
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
136 137
		return 0;

138 139
	old_suffix = diff_pick_suffix(delta->old_file.mode);
	new_suffix = diff_pick_suffix(delta->new_file.mode);
140

141
	git_buf_clear(out);
142 143

	if (delta->old_file.path != delta->new_file.path &&
144
		strcomp(delta->old_file.path,delta->new_file.path) != 0)
145
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
146 147 148
			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)
149 150
		git_buf_printf(out, "%c\t%s%c %s%c\n", code,
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
151
	else if (old_suffix != ' ')
152
		git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
153
	else
154 155
		git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
	if (git_buf_oom(out))
156
		return -1;
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);
163 164
}

165
static int diff_print_one_raw(
166 167 168
	const git_diff_delta *delta, float progress, void *data)
{
	diff_print_info *pi = data;
169
	git_buf *out = pi->buf;
170 171 172 173 174
	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
175
	if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
176 177
		return 0;

178
	git_buf_clear(out);
179

180 181
	git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id);
	git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
182 183

	git_buf_printf(
184 185
		out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
			":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
186 187 188
		delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);

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

191
	if (delta->old_file.path != delta->new_file.path)
192
		git_buf_printf(
193
			out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
194 195
	else
		git_buf_printf(
196
			out, "\t%s\n", delta->old_file.path ?
197 198
			delta->old_file.path : delta->new_file.path);

199
	if (git_buf_oom(out))
200
		return -1;
201

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

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

209 210
static int diff_print_oid_range(
	git_buf *out, const git_diff_delta *delta, int oid_strlen)
211 212 213
{
	char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];

214 215
	git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
	git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
216 217 218

	/* TODO: Match git diff more closely */
	if (delta->old_file.mode == delta->new_file.mode) {
219
		git_buf_printf(out, "index %s..%s %o\n",
220 221 222
			start_oid, end_oid, delta->old_file.mode);
	} else {
		if (delta->old_file.mode == 0) {
223
			git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
224
		} else if (delta->new_file.mode == 0) {
225
			git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
226
		} else {
227 228
			git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
			git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
229
		}
230
		git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
231 232
	}

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

236
static int diff_delta_format_with_paths(
237 238 239 240
	git_buf *out,
	const git_diff_delta *delta,
	const char *oldpfx,
	const char *newpfx,
241
	const char *template)
242 243 244 245
{
	const char *oldpath = delta->old_file.path;
	const char *newpath = delta->new_file.path;

246
	if (git_oid_iszero(&delta->old_file.id)) {
247 248 249
		oldpfx = "";
		oldpath = "/dev/null";
	}
250
	if (git_oid_iszero(&delta->new_file.id)) {
251 252 253 254 255 256 257 258 259 260 261 262 263 264
		newpfx = "";
		newpath = "/dev/null";
	}

	return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
}

int git_diff_delta__format_file_header(
	git_buf *out,
	const git_diff_delta *delta,
	const char *oldpfx,
	const char *newpfx,
	int oid_strlen)
{
265 266 267 268
	if (!oldpfx)
		oldpfx = DIFF_OLD_PREFIX_DEFAULT;
	if (!newpfx)
		newpfx = DIFF_NEW_PREFIX_DEFAULT;
269 270
	if (!oid_strlen)
		oid_strlen = GIT_ABBREV_DEFAULT + 1;
271

272 273 274
	git_buf_clear(out);

	git_buf_printf(out, "diff --git %s%s %s%s\n",
275
		oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
276

277
	GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
278

279 280 281
	if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
		diff_delta_format_with_paths(
			out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
282

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

286 287 288 289
static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new)
{
	git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL;
	const void *old_data, *new_data;
290 291
	git_off_t old_data_len, new_data_len;
	unsigned long delta_data_len, inflated_len;
292
	const char *out_type = "literal";
293
	char *scan, *end;
294 295 296 297 298
	int error;

	old_data = old ? git_blob_rawcontent(old) : NULL;
	new_data = new ? git_blob_rawcontent(new) : NULL;

299 300
	old_data_len = old ? git_blob_rawsize(old) : 0;
	new_data_len = new ? git_blob_rawsize(new) : 0;
301

Russell Belfer committed
302 303 304
	/* The git_delta function accepts unsigned long only */
	if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len))
		return GIT_EBUFS;
305 306

	out = &deflate;
307
	inflated_len = (unsigned long)new_data_len;
308 309

	if ((error = git_zstream_deflatebuf(
Russell Belfer committed
310
			out, new_data, (size_t)new_data_len)) < 0)
311 312
		goto done;

313
	/* The git_delta function accepts unsigned long only */
314 315
	if (!git__is_ulong((git_off_t)deflate.size)) {
		error = GIT_EBUFS;
316 317
		goto done;
	}
318 319

	if (old && new) {
320 321 322 323
		void *delta_data = git_delta(
			old_data, (unsigned long)old_data_len,
			new_data, (unsigned long)new_data_len,
			&delta_data_len, (unsigned long)deflate.size);
324 325

		if (delta_data) {
326 327 328 329
			error = git_zstream_deflatebuf(
				&delta, delta_data, (size_t)delta_data_len);

			git__free(delta_data);
330 331 332 333 334 335 336 337 338 339 340 341

			if (error < 0)
				goto done;

			if (delta.size < deflate.size) {
				out = &delta;
				out_type = "delta";
				inflated_len = delta_data_len;
			}
		}
	}

342
	git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len);
343 344
	pi->line.num_lines++;

345 346 347 348
	for (scan = out->ptr, end = out->ptr + out->size; scan < end; ) {
		size_t chunk_len = end - scan;
		if (chunk_len > 52)
			chunk_len = 52;
349 350

		if (chunk_len <= 26)
351
			git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
352
		else
353
			git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
354

355
		git_buf_encode_base85(pi->buf, scan, chunk_len);
356 357 358 359 360 361 362
		git_buf_putc(pi->buf, '\n');

		if (git_buf_oom(pi->buf)) {
			error = -1;
			goto done;
		}

363
		scan += chunk_len;
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
		pi->line.num_lines++;
	}

done:
	git_buf_free(&deflate);
	git_buf_free(&delta);

	return error;
}

/* git diff --binary 8d7523f~2 8d7523f~1 */
static int diff_print_patch_file_binary(
	diff_print_info *pi, const git_diff_delta *delta,
	const char *oldpfx, const char *newpfx)
{
	git_blob *old = NULL, *new = NULL;
	const git_oid *old_id, *new_id;
	int error;
382
	size_t pre_binary_size;
383

384 385
	if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
		goto noshow;
386

387
	pre_binary_size = pi->buf->size;
388 389 390 391 392 393
	git_buf_printf(pi->buf, "GIT binary patch\n");
	pi->line.num_lines++;

	old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL;
	new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL;

394 395 396 397 398 399
	if (old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0)
		goto done;
	if (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0)
		goto done;

	if ((error = print_binary_hunk(pi, old, new)) < 0 ||
400 401
		(error = git_buf_putc(pi->buf, '\n')) < 0 ||
		(error = print_binary_hunk(pi, new, old)) < 0)
402 403 404 405 406 407 408
	{
		if (error == GIT_EBUFS) {
			giterr_clear();
			git_buf_truncate(pi->buf, pre_binary_size);
			goto noshow;
		}
	}
409 410 411 412 413 414 415 416

	pi->line.num_lines++;

done:
	git_blob_free(old);
	git_blob_free(new);

	return error;
417 418 419 420 421 422

noshow:
	pi->line.num_lines = 1;
	return diff_delta_format_with_paths(
		pi->buf, delta, oldpfx, newpfx,
		"Binary files %s%s and %s%s differ\n");
423 424
}

425 426 427
static int diff_print_patch_file(
	const git_diff_delta *delta, float progress, void *data)
{
428
	int error;
429
	diff_print_info *pi = data;
430 431 432 433
	const char *oldpfx =
		pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
	const char *newpfx =
		pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
434

435 436 437 438 439
	bool binary = !!(delta->flags & GIT_DIFF_FLAG_BINARY);
	bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
	int oid_strlen = binary && show_binary ?
		GIT_OID_HEXSZ + 1 : pi->oid_strlen;

440
	GIT_UNUSED(progress);
441

442 443 444
	if (S_ISDIR(delta->new_file.mode) ||
		delta->status == GIT_DELTA_UNMODIFIED ||
		delta->status == GIT_DELTA_IGNORED ||
445
		delta->status == GIT_DELTA_UNREADABLE ||
446
		(delta->status == GIT_DELTA_UNTRACKED &&
Russell Belfer committed
447
		 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
448 449
		return 0;

450
	if ((error = git_diff_delta__format_file_header(
451
			pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
452
		return error;
453

454 455 456 457
	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);

458 459
	if ((error = pi->print_cb(delta, NULL, &pi->line, pi->payload)) != 0)
		return error;
460

461
	if (!binary)
462 463 464 465
		return 0;

	git_buf_clear(pi->buf);

466
	if ((error = diff_print_patch_file_binary(pi, delta, oldpfx, newpfx)) < 0)
467
		return error;
468

469 470 471 472
	pi->line.origin      = GIT_DIFF_LINE_BINARY;
	pi->line.content     = git_buf_cstr(pi->buf);
	pi->line.content_len = git_buf_len(pi->buf);

473
	return pi->print_cb(delta, NULL, &pi->line, pi->payload);
474 475
}

476
static int diff_print_patch_hunk(
477
	const git_diff_delta *d,
478
	const git_diff_hunk *h,
479 480 481 482 483 484 485
	void *data)
{
	diff_print_info *pi = data;

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

486 487 488
	pi->line.origin      = GIT_DIFF_LINE_HUNK_HDR;
	pi->line.content     = h->header;
	pi->line.content_len = h->header_len;
489

490
	return pi->print_cb(d, h, &pi->line, pi->payload);
491 492
}

493
static int diff_print_patch_line(
494
	const git_diff_delta *delta,
495 496
	const git_diff_hunk *hunk,
	const git_diff_line *line,
497 498 499 500 501 502 503
	void *data)
{
	diff_print_info *pi = data;

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

504
	return pi->print_cb(delta, hunk, line, pi->payload);
505 506
}

Russell Belfer committed
507 508
/* print a git_diff to an output callback */
int git_diff_print(
509
	git_diff *diff,
Russell Belfer committed
510
	git_diff_format_t format,
511
	git_diff_line_cb print_cb,
512 513 514 515 516
	void *payload)
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;
Russell Belfer committed
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
	git_diff_file_cb print_file = NULL;
	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;
		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:
		giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
		return -1;
	}
543

Russell Belfer committed
544 545
	if (!(error = diff_print_info_init(
			&pi, &buf, diff, format, print_cb, payload)))
546
	{
547
		error = git_diff_foreach(
Russell Belfer committed
548
			diff, print_file, print_hunk, print_line, &pi);
549

550
		if (error) /* make sure error message is set */
551
			giterr_set_after_callback_function(error, "git_diff_print");
552 553
	}

554 555 556 557 558
	git_buf_free(&buf);

	return error;
}

559 560 561 562
/* print a git_patch to an output callback */
int git_patch_print(
	git_patch *patch,
	git_diff_line_cb print_cb,
563 564 565 566 567 568 569 570 571
	void *payload)
{
	int error;
	git_buf temp = GIT_BUF_INIT;
	diff_print_info pi;

	assert(patch && print_cb);

	if (!(error = diff_print_info_init(
Russell Belfer committed
572 573
			&pi, &temp, git_patch__diff(patch),
			GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
574
	{
575
		error = git_patch__invoke_callbacks(
576 577
			patch, diff_print_patch_file, diff_print_patch_hunk,
			diff_print_patch_line, &pi);
578

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

583 584 585 586 587
	git_buf_free(&temp);

	return error;
}

588
int git_diff_print_callback__to_buf(
589
	const git_diff_delta *delta,
590 591
	const git_diff_hunk *hunk,
	const git_diff_line *line,
592 593 594
	void *payload)
{
	git_buf *output = payload;
595 596
	GIT_UNUSED(delta); GIT_UNUSED(hunk);

597 598 599 600 601
	if (!output) {
		giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
		return -1;
	}

602 603 604 605 606 607
	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);
608 609
}

610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
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;
}

628
/* print a git_patch to a git_buf */
629
int git_patch_to_buf(git_buf *out, git_patch *patch)
630
{
631 632
	assert(out && patch);
	git_buf_sanitize(out);
633
	return git_patch_print(patch, git_diff_print_callback__to_buf, out);
634
}