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

18
static void diff_output_init(
19 20
	git_diff_output*, const git_diff_options*, git_diff_file_cb,
	git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
21

22
static void diff_output_to_patch(git_diff_output *, git_patch *);
23

24
static void diff_patch_update_binary(git_patch *patch)
25 26 27 28
{
	if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
		return;

29 30
	if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
		(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
31 32
		patch->delta->flags |= GIT_DIFF_FLAG_BINARY;

33 34 35 36
	else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
			 patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
		patch->delta->flags |= GIT_DIFF_FLAG_BINARY;

37 38
	else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
			 (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
39 40 41
		patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}

42
static void diff_patch_init_common(git_patch *patch)
43 44 45 46 47 48
{
	diff_patch_update_binary(patch);

	patch->flags |= GIT_DIFF_PATCH_INITIALIZED;

	if (patch->diff)
49
		git_diff_addref(patch->diff);
50 51
}

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
static int diff_patch_normalize_options(
	git_diff_options *out,
	const git_diff_options *opts)
{
	if (opts) {
		GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
		memcpy(out, opts, sizeof(git_diff_options));
	} else {
		git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT;
		memcpy(out, &default_opts, sizeof(git_diff_options));
	}

	out->old_prefix = opts && opts->old_prefix ?
		git__strdup(opts->old_prefix) :
		git__strdup(DIFF_OLD_PREFIX_DEFAULT);

	out->new_prefix = opts && opts->new_prefix ?
		git__strdup(opts->new_prefix) :
		git__strdup(DIFF_NEW_PREFIX_DEFAULT);

	GITERR_CHECK_ALLOC(out->old_prefix);
	GITERR_CHECK_ALLOC(out->new_prefix);

	return 0;
}

78
static int diff_patch_init_from_diff(
79
	git_patch *patch, git_diff *diff, size_t delta_index)
80 81 82 83 84 85 86 87
{
	int error = 0;

	memset(patch, 0, sizeof(*patch));
	patch->diff  = diff;
	patch->delta = git_vector_get(&diff->deltas, delta_index);
	patch->delta_index = delta_index;

88 89
	if ((error = diff_patch_normalize_options(
			&patch->diff_opts, &diff->opts)) < 0 ||
90
		(error = git_diff_file_content__init_from_diff(
91 92 93
			&patch->ofile, diff, patch->delta, true)) < 0 ||
		(error = git_diff_file_content__init_from_diff(
			&patch->nfile, diff, patch->delta, false)) < 0)
94 95 96 97 98 99 100 101
		return error;

	diff_patch_init_common(patch);

	return 0;
}

static int diff_patch_alloc_from_diff(
Russell Belfer committed
102
	git_patch **out, git_diff *diff, size_t delta_index)
103 104
{
	int error;
105
	git_patch *patch = git__calloc(1, sizeof(git_patch));
106 107 108 109 110 111 112 113 114 115 116 117 118 119
	GITERR_CHECK_ALLOC(patch);

	if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
		patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
		GIT_REFCOUNT_INC(patch);
	} else {
		git__free(patch);
		patch = NULL;
	}

	*out = patch;
	return error;
}

120 121 122 123 124 125 126 127
GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file)
{
	if ((patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
		return false;

	return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
}

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
static bool diff_patch_diffable(git_patch *patch)
{
	size_t olen, nlen;

	if (patch->delta->status == GIT_DELTA_UNMODIFIED)
		return false;

	/* if we've determined this to be binary (and we are not showing binary
	 * data) then we have skipped loading the map data.  instead, query the
	 * file data itself.
	 */
	if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
		(patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
		olen = (size_t)patch->ofile.file->size;
		nlen = (size_t)patch->nfile.file->size;
	} else {
		olen = patch->ofile.map.len;
		nlen = patch->nfile.map.len;
	}

	/* if both sides are empty, files are identical */
	if (!olen && !nlen)
		return false;

	/* otherwise, check the file sizes and the oid */
	return (olen != nlen ||
		!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
}

157
static int diff_patch_load(git_patch *patch, git_diff_output *output)
158 159 160 161 162 163 164 165 166 167
{
	int error = 0;
	bool incomplete_data;

	if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
		return 0;

	/* if no hunk and data callbacks and user doesn't care if data looks
	 * binary, then there is no need to actually load the data
	 */
168
	if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
169
		output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
170 171 172
		return 0;

	incomplete_data =
173
		(((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
174
		  (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
175
		 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
176
		  (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
177 178 179 180 181

	/* always try to load workdir content first because filtering may
	 * need 2x data size and this minimizes peak memory footprint
	 */
	if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
182 183 184
		if ((error = git_diff_file_content__load(
				&patch->ofile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->ofile.file))
185 186 187
			goto cleanup;
	}
	if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
188 189 190
		if ((error = git_diff_file_content__load(
				&patch->nfile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->nfile.file))
191 192 193 194 195
			goto cleanup;
	}

	/* once workdir has been tried, load other data as needed */
	if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
196 197 198
		if ((error = git_diff_file_content__load(
				&patch->ofile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->ofile.file))
199 200 201
			goto cleanup;
	}
	if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
202 203 204
		if ((error = git_diff_file_content__load(
				&patch->nfile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->nfile.file))
205 206 207
			goto cleanup;
	}

208 209 210
	/* if previously missing an oid, and now that we have it the two sides
	 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
	 */
211
	if (incomplete_data &&
212
		patch->ofile.file->mode == patch->nfile.file->mode &&
213
		patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
214
		git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
215
		patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
216 217 218 219 220 221
		patch->delta->status = GIT_DELTA_UNMODIFIED;

cleanup:
	diff_patch_update_binary(patch);

	if (!error) {
222
		if (diff_patch_diffable(patch))
223 224 225 226 227 228 229 230
			patch->flags |= GIT_DIFF_PATCH_DIFFABLE;

		patch->flags |= GIT_DIFF_PATCH_LOADED;
	}

	return error;
}

231
static int diff_patch_invoke_file_callback(
232
	git_patch *patch, git_diff_output *output)
233
{
234
	float progress = patch->diff ?
235 236
		((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;

237 238
	if (!output->file_cb)
		return 0;
239

240
	return giterr_set_after_callback_function(
241 242
		output->file_cb(patch->delta, progress, output->payload),
		"git_patch");
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 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
static int create_binary(
	git_diff_binary_t *out_type,
	char **out_data,
	size_t *out_datalen,
	size_t *out_inflatedlen,
	const char *a_data,
	size_t a_datalen,
	const char *b_data,
	size_t b_datalen)
{
	git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
	unsigned long delta_data_len;
	int error;

	/* The git_delta function accepts unsigned long only */
	if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
		return GIT_EBUFS;

	if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
		goto done;

	/* The git_delta function accepts unsigned long only */
	if (!git__is_ulong(deflate.size)) {
		error = GIT_EBUFS;
		goto done;
	}

	if (a_datalen && b_datalen) {
		void *delta_data = git_delta(
			a_data, (unsigned long)a_datalen,
			b_data, (unsigned long)b_datalen,
			&delta_data_len, (unsigned long)deflate.size);

		if (delta_data) {
			error = git_zstream_deflatebuf(
				&delta, delta_data, (size_t)delta_data_len);

			git__free(delta_data);

			if (error < 0)
				goto done;
		}
	}

	if (delta.size && delta.size < deflate.size) {
		*out_type = GIT_DIFF_BINARY_DELTA;
		*out_datalen = delta.size;
		*out_data = git_buf_detach(&delta);
		*out_inflatedlen = delta_data_len;
	} else {
		*out_type = GIT_DIFF_BINARY_LITERAL;
		*out_datalen = deflate.size;
		*out_data = git_buf_detach(&deflate);
		*out_inflatedlen = b_datalen;
	}

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

	return error;
}

static int diff_binary(git_diff_output *output, git_patch *patch)
{
310
	git_diff_binary binary = {{0}};
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	const char *old_data = patch->ofile.map.data;
	const char *new_data = patch->nfile.map.data;
	size_t old_len = patch->ofile.map.len,
		new_len = patch->nfile.map.len;
	int error;

	/* Create the old->new delta (as the "new" side of the patch),
	 * and the new->old delta (as the "old" side)
	 */
	if ((error = create_binary(&binary.old_file.type,
			(char **)&binary.old_file.data,
			&binary.old_file.datalen,
			&binary.old_file.inflatedlen,
			new_data, new_len, old_data, old_len)) < 0 ||
		(error = create_binary(&binary.new_file.type,
			(char **)&binary.new_file.data,
			&binary.new_file.datalen,
			&binary.new_file.inflatedlen,
			old_data, old_len, new_data, new_len)) < 0)
		return error;

332
	error = giterr_set_after_callback_function(
333 334
		output->binary_cb(patch->delta, &binary, output->payload),
		"git_patch");
335 336 337 338 339

	git__free((char *) binary.old_file.data);
	git__free((char *) binary.new_file.data);

	return error;
340 341
}

342
static int diff_patch_generate(git_patch *patch, git_diff_output *output)
343 344 345 346 347 348
{
	int error = 0;

	if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
		return 0;

349 350
	/* if we are not looking at the binary or text data, don't do the diff */
	if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
351 352
		return 0;

353 354 355 356 357 358 359
	if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
		(error = diff_patch_load(patch, output)) < 0)
		return error;

	if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
		return 0;

360 361 362 363 364 365 366 367
	if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
		if (output->binary_cb)
			error = diff_binary(output, patch);
	}
	else {
		if (output->diff_cb)
			error = output->diff_cb(output, patch);
	}
368

369
	patch->flags |= GIT_DIFF_PATCH_DIFFED;
370 371 372
	return error;
}

373
static void diff_patch_free(git_patch *patch)
374
{
375 376
	git_diff_file_content__clear(&patch->ofile);
	git_diff_file_content__clear(&patch->nfile);
377 378 379 380

	git_array_clear(patch->lines);
	git_array_clear(patch->hunks);

381
	git_diff_free(patch->diff); /* decrements refcount */
382 383 384 385
	patch->diff = NULL;

	git_pool_clear(&patch->flattened);

386 387 388
	git__free((char *)patch->diff_opts.old_prefix);
	git__free((char *)patch->diff_opts.new_prefix);

389 390 391
	git__free((char *)patch->binary.old_file.data);
	git__free((char *)patch->binary.new_file.data);

392 393 394 395
	if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
		git__free(patch);
}

396
static int diff_required(git_diff *diff, const char *action)
397 398 399 400 401 402 403 404
{
	if (diff)
		return 0;
	giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
	return -1;
}

int git_diff_foreach(
405
	git_diff *diff,
406
	git_diff_file_cb file_cb,
407
	git_diff_binary_cb binary_cb,
408
	git_diff_hunk_cb hunk_cb,
409
	git_diff_line_cb data_cb,
410 411 412 413 414
	void *payload)
{
	int error = 0;
	git_xdiff_output xo;
	size_t idx;
415
	git_patch patch;
416

417 418
	if ((error = diff_required(diff, "git_diff_foreach")) < 0)
		return error;
419

420
	memset(&xo, 0, sizeof(xo));
421
	memset(&patch, 0, sizeof(patch));
Russell Belfer committed
422
	diff_output_init(
423
		&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
424 425 426
	git_xdiff_init(&xo, &diff->opts);

	git_vector_foreach(&diff->deltas, idx, patch.delta) {
427

428 429 430 431
		/* check flags against patch status */
		if (git_diff_delta__should_skip(&diff->opts, patch.delta))
			continue;

432 433 434 435 436 437
		if (binary_cb || hunk_cb || data_cb) {
			if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 ||
				(error = diff_patch_load(&patch, &xo.output)) != 0)
				return error;
		}

438
		if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
439
			if (binary_cb || hunk_cb || data_cb)
440 441
					error = diff_patch_generate(&patch, &xo.output);
		}
442

443
		git_patch_free(&patch);
444

445
		if (error)
446 447 448 449 450 451 452
			break;
	}

	return error;
}

typedef struct {
453
	git_patch patch;
454
	git_diff_delta delta;
455
	char paths[GIT_FLEX_ARRAY];
456
} diff_patch_with_delta;
457

458
static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
459 460
{
	int error = 0;
461
	git_patch *patch = &pd->patch;
462 463
	bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
	bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
464

465
	pd->delta.status = has_new ?
466 467 468
		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);

469
	if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
470
		pd->delta.status = GIT_DELTA_UNMODIFIED;
471

472
	patch->delta = &pd->delta;
473 474 475

	diff_patch_init_common(patch);

476 477 478 479
	if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
		!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
		return error;

480
	error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
481 482

	if (!error)
483
		error = diff_patch_generate(patch, (git_diff_output *)xo);
484 485 486 487

	return error;
}

488
static int diff_patch_from_sources(
489 490
	diff_patch_with_delta *pd,
	git_xdiff_output *xo,
491 492
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
493
	const git_diff_options *opts)
494 495 496
{
	int error = 0;
	git_repository *repo =
497 498 499 500
		oldsrc->blob ? git_blob_owner(oldsrc->blob) :
		newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
	git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
	git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
501

502 503
	if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0)
		return error;
504 505

	if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
506 507
		void *tmp = lfile; lfile = rfile; rfile = tmp;
		tmp = ldata; ldata = rdata; rdata = tmp;
508 509
	}

510 511
	pd->patch.delta = &pd->delta;

512 513 514 515 516 517 518 519 520 521 522
	if (!oldsrc->as_path) {
		if (newsrc->as_path)
			oldsrc->as_path = newsrc->as_path;
		else
			oldsrc->as_path = newsrc->as_path = "file";
	}
	else if (!newsrc->as_path)
		newsrc->as_path = oldsrc->as_path;

	lfile->path = oldsrc->as_path;
	rfile->path = newsrc->as_path;
523

524 525 526 527
	if ((error = git_diff_file_content__init_from_src(
			ldata, repo, opts, oldsrc, lfile)) < 0 ||
		(error = git_diff_file_content__init_from_src(
			rdata, repo, opts, newsrc, rfile)) < 0)
528 529 530 531 532
		return error;

	return diff_single_generate(pd, xo);
}

533 534 535 536 537 538 539 540
static int diff_patch_with_delta_alloc(
	diff_patch_with_delta **out,
	const char **old_path,
	const char **new_path)
{
	diff_patch_with_delta *pd;
	size_t old_len = *old_path ? strlen(*old_path) : 0;
	size_t new_len = *new_path ? strlen(*new_path) : 0;
541
	size_t alloc_len;
542

543 544 545
	GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
546 547

	*out = pd = git__calloc(1, alloc_len);
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
	GITERR_CHECK_ALLOC(pd);

	pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;

	if (*old_path) {
		memcpy(&pd->paths[0], *old_path, old_len);
		*old_path = &pd->paths[0];
	} else if (*new_path)
		*old_path = &pd->paths[old_len + 1];

	if (*new_path) {
		memcpy(&pd->paths[old_len + 1], *new_path, new_len);
		*new_path = &pd->paths[old_len + 1];
	} else if (*old_path)
		*new_path = &pd->paths[0];

	return 0;
}

567 568 569
static int diff_from_sources(
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
570 571
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
572
	git_diff_binary_cb binary_cb,
573
	git_diff_hunk_cb hunk_cb,
574
	git_diff_line_cb data_cb,
575 576 577 578 579 580 581 582
	void *payload)
{
	int error = 0;
	diff_patch_with_delta pd;
	git_xdiff_output xo;

	memset(&xo, 0, sizeof(xo));
	diff_output_init(
583
		&xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
584
	git_xdiff_init(&xo, opts);
585

586
	memset(&pd, 0, sizeof(pd));
587 588

	error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
589

590
	git_patch_free(&pd.patch);
591 592 593 594

	return error;
}

595
static int patch_from_sources(
596
	git_patch **out,
597 598
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
599 600 601 602 603 604 605 606 607
	const git_diff_options *opts)
{
	int error = 0;
	diff_patch_with_delta *pd;
	git_xdiff_output xo;

	assert(out);
	*out = NULL;

608 609 610
	if ((error = diff_patch_with_delta_alloc(
			&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
		return error;
611 612

	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
613
	diff_output_to_patch(&xo.output, &pd->patch);
614 615
	git_xdiff_init(&xo, opts);

616
	if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
617
		*out = (git_patch *)pd;
618
	else
619
		git_patch_free((git_patch *)pd);
620 621 622 623

	return error;
}

624
int git_diff_blobs(
625
	const git_blob *old_blob,
626
	const char *old_path,
627 628 629 630
	const git_blob *new_blob,
	const char *new_path,
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
631
	git_diff_binary_cb binary_cb,
632 633 634
	git_diff_hunk_cb hunk_cb,
	git_diff_line_cb data_cb,
	void *payload)
635
{
636 637 638 639 640
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
	return diff_from_sources(
641
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
642
}
643

644 645 646 647 648 649 650 651 652 653 654 655 656
int git_patch_from_blobs(
	git_patch **out,
	const git_blob *old_blob,
	const char *old_path,
	const git_blob *new_blob,
	const char *new_path,
	const git_diff_options *opts)
{
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
	return patch_from_sources(out, &osrc, &nsrc, opts);
657 658 659 660
}

int git_diff_blob_to_buffer(
	const git_blob *old_blob,
661
	const char *old_path,
662 663
	const char *buf,
	size_t buflen,
664
	const char *buf_path,
665 666
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
667
	git_diff_binary_cb binary_cb,
668
	git_diff_hunk_cb hunk_cb,
669
	git_diff_line_cb data_cb,
670 671
	void *payload)
{
672 673 674 675 676
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
	return diff_from_sources(
677
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
678 679
}

680 681
int git_patch_from_blob_and_buffer(
	git_patch **out,
682
	const git_blob *old_blob,
683
	const char *old_path,
684 685
	const char *buf,
	size_t buflen,
686
	const char *buf_path,
687 688
	const git_diff_options *opts)
{
689 690 691 692 693 694
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
	return patch_from_sources(out, &osrc, &nsrc, opts);
}
695

696 697 698 699 700 701 702 703 704
int git_diff_buffers(
	const void *old_buf,
	size_t old_len,
	const char *old_path,
	const void *new_buf,
	size_t new_len,
	const char *new_path,
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
705
	git_diff_binary_cb binary_cb,
706 707 708 709 710 711 712 713 714
	git_diff_hunk_cb hunk_cb,
	git_diff_line_cb data_cb,
	void *payload)
{
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
	return diff_from_sources(
715
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
716
}
717

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
int git_patch_from_buffers(
	git_patch **out,
	const void *old_buf,
	size_t old_len,
	const char *old_path,
	const char *new_buf,
	size_t new_len,
	const char *new_path,
	const git_diff_options *opts)
{
	git_diff_file_content_src osrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
	git_diff_file_content_src nsrc =
		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
	return patch_from_sources(out, &osrc, &nsrc, opts);
733 734
}

735
int git_patch_from_diff(
Russell Belfer committed
736
	git_patch **patch_ptr, git_diff *diff, size_t idx)
737 738 739 740
{
	int error = 0;
	git_xdiff_output xo;
	git_diff_delta *delta = NULL;
741
	git_patch *patch = NULL;
742 743 744

	if (patch_ptr) *patch_ptr = NULL;

745
	if (diff_required(diff, "git_patch_from_diff") < 0)
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
		return -1;

	delta = git_vector_get(&diff->deltas, idx);
	if (!delta) {
		giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
		return GIT_ENOTFOUND;
	}

	if (git_diff_delta__should_skip(&diff->opts, delta))
		return 0;

	/* don't load the patch data unless we need it for binary check */
	if (!patch_ptr &&
		((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
		 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
		return 0;

	if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
		return error;

766
	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
767
	diff_output_to_patch(&xo.output, patch);
768 769
	git_xdiff_init(&xo, &diff->opts);

770
	error = diff_patch_invoke_file_callback(patch, &xo.output);
771 772

	if (!error)
Russell Belfer committed
773
		error = diff_patch_generate(patch, &xo.output);
774 775

	if (!error) {
776 777
		/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
		/* TODO: and unload the file content */
778 779 780
	}

	if (error || !patch_ptr)
781
		git_patch_free(patch);
782 783 784 785 786 787
	else
		*patch_ptr = patch;

	return error;
}

788
void git_patch_free(git_patch *patch)
789 790 791 792 793
{
	if (patch)
		GIT_REFCOUNT_DEC(patch, diff_patch_free);
}

794
const git_diff_delta *git_patch_get_delta(const git_patch *patch)
795 796 797 798 799
{
	assert(patch);
	return patch->delta;
}

800
size_t git_patch_num_hunks(const git_patch *patch)
801 802 803 804 805
{
	assert(patch);
	return git_array_size(patch->hunks);
}

806
int git_patch_line_stats(
807 808 809
	size_t *total_ctxt,
	size_t *total_adds,
	size_t *total_dels,
810
	const git_patch *patch)
811 812 813 814 815 816
{
	size_t totals[3], idx;

	memset(totals, 0, sizeof(totals));

	for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
817
		git_diff_line *line = git_array_get(patch->lines, idx);
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
		if (!line)
			continue;

		switch (line->origin) {
		case GIT_DIFF_LINE_CONTEXT:  totals[0]++; break;
		case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
		case GIT_DIFF_LINE_DELETION: totals[2]++; break;
		default:
			/* diff --stat and --numstat don't count EOFNL marks because
			 * they will always be paired with a ADDITION or DELETION line.
			 */
			break;
		}
	}

	if (total_ctxt)
		*total_ctxt = totals[0];
	if (total_adds)
		*total_adds = totals[1];
	if (total_dels)
		*total_dels = totals[2];

	return 0;
}

static int diff_error_outofrange(const char *thing)
{
	giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
	return GIT_ENOTFOUND;
}

849 850
int git_patch_get_hunk(
	const git_diff_hunk **out,
851
	size_t *lines_in_hunk,
852
	git_patch *patch,
853 854 855 856 857 858 859 860
	size_t hunk_idx)
{
	diff_patch_hunk *hunk;
	assert(patch);

	hunk = git_array_get(patch->hunks, hunk_idx);

	if (!hunk) {
861
		if (out) *out = NULL;
862 863 864 865
		if (lines_in_hunk) *lines_in_hunk = 0;
		return diff_error_outofrange("hunk");
	}

866
	if (out) *out = &hunk->hunk;
867 868 869 870
	if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
	return 0;
}

871
int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
872 873 874 875 876 877 878 879 880
{
	diff_patch_hunk *hunk;
	assert(patch);

	if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
		return diff_error_outofrange("hunk");
	return (int)hunk->line_count;
}

881
int git_patch_get_line_in_hunk(
882
	const git_diff_line **out,
883
	git_patch *patch,
884 885 886 887
	size_t hunk_idx,
	size_t line_of_hunk)
{
	diff_patch_hunk *hunk;
888
	git_diff_line *line;
889 890 891 892

	assert(patch);

	if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
893 894
		if (out) *out = NULL;
		return diff_error_outofrange("hunk");
895 896 897 898 899
	}

	if (line_of_hunk >= hunk->line_count ||
		!(line = git_array_get(
			patch->lines, hunk->line_start + line_of_hunk))) {
900 901
		if (out) *out = NULL;
		return diff_error_outofrange("line");
902 903
	}

904
	if (out) *out = line;
905 906 907
	return 0;
}

908 909
size_t git_patch_size(
	git_patch *patch,
910 911 912
	int include_context,
	int include_hunk_headers,
	int include_file_headers)
913 914 915 916 917 918 919 920 921 922
{
	size_t out;

	assert(patch);

	out = patch->content_size;

	if (!include_context)
		out -= patch->context_size;

923 924 925 926 927 928 929 930 931 932 933 934 935 936 937
	if (include_hunk_headers)
		out += patch->header_size;

	if (include_file_headers) {
		git_buf file_header = GIT_BUF_INIT;

		if (git_diff_delta__format_file_header(
				&file_header, patch->delta, NULL, NULL, 0) < 0)
			giterr_clear();
		else
			out += git_buf_len(&file_header);

		git_buf_free(&file_header);
	}

938 939 940
	return out;
}

941
git_diff *git_patch__diff(git_patch *patch)
942 943 944 945
{
	return patch->diff;
}

946
git_diff_driver *git_patch__driver(git_patch *patch)
947 948 949 950 951
{
	/* ofile driver is representative for whole patch */
	return patch->ofile.driver;
}

952 953
void git_patch__old_data(
	char **ptr, size_t *len, git_patch *patch)
954 955 956 957 958
{
	*ptr = patch->ofile.map.data;
	*len = patch->ofile.map.len;
}

959 960
void git_patch__new_data(
	char **ptr, size_t *len, git_patch *patch)
961 962 963 964 965
{
	*ptr = patch->nfile.map.data;
	*len = patch->nfile.map.len;
}

966 967
int git_patch__invoke_callbacks(
	git_patch *patch,
968
	git_diff_file_cb file_cb,
969
	git_diff_binary_cb binary_cb,
970
	git_diff_hunk_cb hunk_cb,
971
	git_diff_line_cb line_cb,
972 973 974 975 976 977 978 979
	void *payload)
{
	int error = 0;
	uint32_t i, j;

	if (file_cb)
		error = file_cb(patch->delta, 0, payload);

980 981 982 983 984 985 986
	if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
		if (binary_cb)
			error = binary_cb(patch->delta, &patch->binary, payload);

		return error;
	}

987 988 989 990 991 992
	if (!hunk_cb && !line_cb)
		return error;

	for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
		diff_patch_hunk *h = git_array_get(patch->hunks, i);

993 994
		if (hunk_cb)
			error = hunk_cb(patch->delta, &h->hunk, payload);
995 996 997 998 999

		if (!line_cb)
			continue;

		for (j = 0; !error && j < h->line_count; ++j) {
1000
			git_diff_line *l =
1001 1002
				git_array_get(patch->lines, h->line_start + j);

1003
			error = line_cb(patch->delta, &h->hunk, l, payload);
1004 1005 1006 1007 1008 1009
		}
	}

	return error;
}

1010 1011 1012 1013 1014 1015

static int diff_patch_file_cb(
	const git_diff_delta *delta,
	float progress,
	void *payload)
{
1016
	GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
1017 1018 1019
	return 0;
}

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
static int diff_patch_binary_cb(
	const git_diff_delta *delta,
	const git_diff_binary *binary,
	void *payload)
{
	git_patch *patch = payload;

	GIT_UNUSED(delta);

	memcpy(&patch->binary, binary, sizeof(git_diff_binary));

	if (binary->old_file.data) {
		patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
		GITERR_CHECK_ALLOC(patch->binary.old_file.data);

		memcpy((char *)patch->binary.old_file.data,
			binary->old_file.data, binary->old_file.datalen);
	}

	if (binary->new_file.data) {
		patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
		GITERR_CHECK_ALLOC(patch->binary.new_file.data);

		memcpy((char *)patch->binary.new_file.data,
			binary->new_file.data, binary->new_file.datalen);
	}

	return 0;
}

1050 1051
static int diff_patch_hunk_cb(
	const git_diff_delta *delta,
1052
	const git_diff_hunk *hunk_,
1053 1054
	void *payload)
{
1055
	git_patch *patch = payload;
1056 1057 1058 1059
	diff_patch_hunk *hunk;

	GIT_UNUSED(delta);

1060
	hunk = git_array_alloc(patch->hunks);
1061
	GITERR_CHECK_ALLOC(hunk);
1062

1063
	memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
1064

1065
	patch->header_size += hunk_->header_len;
1066

1067 1068 1069 1070 1071 1072 1073 1074
	hunk->line_start = git_array_size(patch->lines);
	hunk->line_count = 0;

	return 0;
}

static int diff_patch_line_cb(
	const git_diff_delta *delta,
1075
	const git_diff_hunk *hunk_,
1076
	const git_diff_line *line_,
1077 1078
	void *payload)
{
1079
	git_patch *patch = payload;
1080
	diff_patch_hunk *hunk;
1081
	git_diff_line   *line;
1082 1083

	GIT_UNUSED(delta);
1084
	GIT_UNUSED(hunk_);
1085 1086

	hunk = git_array_last(patch->hunks);
1087
	assert(hunk); /* programmer error if no hunk is available */
1088

1089
	line = git_array_alloc(patch->lines);
1090
	GITERR_CHECK_ALLOC(line);
1091

1092
	memcpy(line, line_, sizeof(*line));
1093 1094 1095

	/* do some bookkeeping so we can provide old/new line numbers */

1096
	patch->content_size += line->content_len;
1097

1098 1099
	if (line->origin == GIT_DIFF_LINE_ADDITION ||
		line->origin == GIT_DIFF_LINE_DELETION)
1100
		patch->content_size += 1;
1101
	else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
1102
		patch->content_size += 1;
1103 1104 1105
		patch->context_size += line->content_len + 1;
	} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
		patch->context_size += line->content_len;
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115

	hunk->line_count++;

	return 0;
}

static void diff_output_init(
	git_diff_output *out,
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
1116
	git_diff_binary_cb binary_cb,
1117
	git_diff_hunk_cb hunk_cb,
1118
	git_diff_line_cb data_cb,
1119 1120 1121 1122 1123 1124 1125
	void *payload)
{
	GIT_UNUSED(opts);

	memset(out, 0, sizeof(*out));

	out->file_cb = file_cb;
1126
	out->binary_cb = binary_cb;
1127 1128 1129 1130 1131
	out->hunk_cb = hunk_cb;
	out->data_cb = data_cb;
	out->payload = payload;
}

1132
static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
1133 1134
{
	diff_output_init(
1135 1136 1137 1138 1139 1140 1141
		out,
		NULL,
		diff_patch_file_cb,
		diff_patch_binary_cb,
		diff_patch_hunk_cb,
		diff_patch_line_cb,
		patch);
1142
}