diff_patch.c 27.4 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
	else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
			 (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
35 36 37
		patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}

38
static void diff_patch_init_common(git_patch *patch)
39 40 41 42 43 44
{
	diff_patch_update_binary(patch);

	patch->flags |= GIT_DIFF_PATCH_INITIALIZED;

	if (patch->diff)
45
		git_diff_addref(patch->diff);
46 47
}

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
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;
}

74
static int diff_patch_init_from_diff(
75
	git_patch *patch, git_diff *diff, size_t delta_index)
76 77 78 79 80 81 82 83
{
	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;

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

	diff_patch_init_common(patch);

	return 0;
}

static int diff_patch_alloc_from_diff(
Russell Belfer committed
98
	git_patch **out, git_diff *diff, size_t delta_index)
99 100
{
	int error;
101
	git_patch *patch = git__calloc(1, sizeof(git_patch));
102 103 104 105 106 107 108 109 110 111 112 113 114 115
	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;
}

116 117 118 119 120 121 122 123
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;
}

124 125 126 127 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
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));
}

153
static int diff_patch_load(git_patch *patch, git_diff_output *output)
154 155 156 157 158 159 160 161 162 163
{
	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
	 */
164
	if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
165
		output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
166 167 168
		return 0;

	incomplete_data =
169
		(((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
170
		  (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
171
		 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
172
		  (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
173 174 175 176 177

	/* 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) {
178 179 180
		if ((error = git_diff_file_content__load(
				&patch->ofile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->ofile.file))
181 182 183
			goto cleanup;
	}
	if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
184 185 186
		if ((error = git_diff_file_content__load(
				&patch->nfile, &patch->diff_opts)) < 0 ||
			should_skip_binary(patch, patch->nfile.file))
187 188 189 190 191
			goto cleanup;
	}

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

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

cleanup:
	diff_patch_update_binary(patch);

	if (!error) {
218
		if (diff_patch_diffable(patch))
219 220 221 222 223 224 225 226
			patch->flags |= GIT_DIFF_PATCH_DIFFABLE;

		patch->flags |= GIT_DIFF_PATCH_LOADED;
	}

	return error;
}

227
static int diff_patch_invoke_file_callback(
228
	git_patch *patch, git_diff_output *output)
229
{
230
	float progress = patch->diff ?
231 232
		((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;

233 234
	if (!output->file_cb)
		return 0;
235

236
	return giterr_set_after_callback_function(
237 238
		output->file_cb(patch->delta, progress, output->payload),
		"git_patch");
239 240
}

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 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
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)
{
306
	git_diff_binary binary = {{0}};
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
	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;

328
	error = giterr_set_after_callback_function(
329 330
		output->binary_cb(patch->delta, &binary, output->payload),
		"git_patch");
331 332 333 334 335

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

	return error;
336 337
}

338
static int diff_patch_generate(git_patch *patch, git_diff_output *output)
339 340 341 342 343 344
{
	int error = 0;

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

345 346
	/* 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)
347 348
		return 0;

349 350 351 352 353 354 355
	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;

356 357 358 359 360 361 362 363
	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);
	}
364

365
	patch->flags |= GIT_DIFF_PATCH_DIFFED;
366 367 368
	return error;
}

369
static void diff_patch_free(git_patch *patch)
370
{
371 372
	git_diff_file_content__clear(&patch->ofile);
	git_diff_file_content__clear(&patch->nfile);
373 374 375 376

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

377
	git_diff_free(patch->diff); /* decrements refcount */
378 379 380 381
	patch->diff = NULL;

	git_pool_clear(&patch->flattened);

382 383 384
	git__free((char *)patch->diff_opts.old_prefix);
	git__free((char *)patch->diff_opts.new_prefix);

385 386 387
	git__free((char *)patch->binary.old_file.data);
	git__free((char *)patch->binary.new_file.data);

388 389 390 391
	if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
		git__free(patch);
}

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

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

413 414
	if ((error = diff_required(diff, "git_diff_foreach")) < 0)
		return error;
415

416
	memset(&xo, 0, sizeof(xo));
417
	memset(&patch, 0, sizeof(patch));
Russell Belfer committed
418
	diff_output_init(
419
		&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
420 421 422
	git_xdiff_init(&xo, &diff->opts);

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

424 425 426 427
		/* check flags against patch status */
		if (git_diff_delta__should_skip(&diff->opts, patch.delta))
			continue;

428 429 430 431 432 433
		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;
		}

434
		if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
435
			if (binary_cb || hunk_cb || data_cb)
436 437
					error = diff_patch_generate(&patch, &xo.output);
		}
438

439
		git_patch_free(&patch);
440

441
		if (error)
442 443 444 445 446 447 448
			break;
	}

	return error;
}

typedef struct {
449
	git_patch patch;
450
	git_diff_delta delta;
451
	char paths[GIT_FLEX_ARRAY];
452
} diff_patch_with_delta;
453

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

461
	pd->delta.status = has_new ?
462 463 464
		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);

465
	if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
466
		pd->delta.status = GIT_DELTA_UNMODIFIED;
467

468
	patch->delta = &pd->delta;
469 470 471

	diff_patch_init_common(patch);

472 473 474 475
	if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
		!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
		return error;

476
	error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
477 478

	if (!error)
479
		error = diff_patch_generate(patch, (git_diff_output *)xo);
480 481 482 483

	return error;
}

484
static int diff_patch_from_sources(
485 486
	diff_patch_with_delta *pd,
	git_xdiff_output *xo,
487 488
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
489
	const git_diff_options *opts)
490 491 492
{
	int error = 0;
	git_repository *repo =
493 494 495 496
		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;
497

498 499
	if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0)
		return error;
500 501

	if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
502 503
		void *tmp = lfile; lfile = rfile; rfile = tmp;
		tmp = ldata; ldata = rdata; rdata = tmp;
504 505
	}

506 507
	pd->patch.delta = &pd->delta;

508 509 510 511 512 513 514 515 516 517 518
	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;
519

520 521 522 523
	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)
524 525 526 527 528
		return error;

	return diff_single_generate(pd, xo);
}

529 530 531 532 533 534 535 536
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;
537
	size_t alloc_len;
538

539 540 541
	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);
542 543

	*out = pd = git__calloc(1, alloc_len);
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
	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;
}

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

	memset(&xo, 0, sizeof(xo));
	diff_output_init(
579
		&xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
580
	git_xdiff_init(&xo, opts);
581

582
	memset(&pd, 0, sizeof(pd));
583 584

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

586
	git_patch_free(&pd.patch);
587 588 589 590

	return error;
}

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

	assert(out);
	*out = NULL;

604 605 606
	if ((error = diff_patch_with_delta_alloc(
			&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
		return error;
607 608

	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
609
	diff_output_to_patch(&xo.output, &pd->patch);
610 611
	git_xdiff_init(&xo, opts);

612
	if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
613
		*out = (git_patch *)pd;
614
	else
615
		git_patch_free((git_patch *)pd);
616 617 618 619

	return error;
}

620
int git_diff_blobs(
621
	const git_blob *old_blob,
622
	const char *old_path,
623 624 625 626
	const git_blob *new_blob,
	const char *new_path,
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
627
	git_diff_binary_cb binary_cb,
628 629 630
	git_diff_hunk_cb hunk_cb,
	git_diff_line_cb data_cb,
	void *payload)
631
{
632 633 634 635 636
	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(
637
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
638
}
639

640 641 642 643 644 645 646 647 648 649 650 651 652
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);
653 654 655 656
}

int git_diff_blob_to_buffer(
	const git_blob *old_blob,
657
	const char *old_path,
658 659
	const char *buf,
	size_t buflen,
660
	const char *buf_path,
661 662
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
663
	git_diff_binary_cb binary_cb,
664
	git_diff_hunk_cb hunk_cb,
665
	git_diff_line_cb data_cb,
666 667
	void *payload)
{
668 669 670 671 672
	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(
673
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
674 675
}

676 677
int git_patch_from_blob_and_buffer(
	git_patch **out,
678
	const git_blob *old_blob,
679
	const char *old_path,
680 681
	const char *buf,
	size_t buflen,
682
	const char *buf_path,
683 684
	const git_diff_options *opts)
{
685 686 687 688 689 690
	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);
}
691

692 693 694 695 696 697 698 699 700
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,
701
	git_diff_binary_cb binary_cb,
702 703 704 705 706 707 708 709 710
	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(
711
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
712
}
713

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
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);
729 730
}

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

	if (patch_ptr) *patch_ptr = NULL;

741
	if (diff_required(diff, "git_patch_from_diff") < 0)
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
		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;

762
	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
763
	diff_output_to_patch(&xo.output, patch);
764 765
	git_xdiff_init(&xo, &diff->opts);

766
	error = diff_patch_invoke_file_callback(patch, &xo.output);
767 768

	if (!error)
Russell Belfer committed
769
		error = diff_patch_generate(patch, &xo.output);
770 771

	if (!error) {
772 773
		/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
		/* TODO: and unload the file content */
774 775 776
	}

	if (error || !patch_ptr)
777
		git_patch_free(patch);
778 779 780 781 782 783
	else
		*patch_ptr = patch;

	return error;
}

784
void git_patch_free(git_patch *patch)
785 786 787 788 789
{
	if (patch)
		GIT_REFCOUNT_DEC(patch, diff_patch_free);
}

790
const git_diff_delta *git_patch_get_delta(const git_patch *patch)
791 792 793 794 795
{
	assert(patch);
	return patch->delta;
}

796
size_t git_patch_num_hunks(const git_patch *patch)
797 798 799 800 801
{
	assert(patch);
	return git_array_size(patch->hunks);
}

802
int git_patch_line_stats(
803 804 805
	size_t *total_ctxt,
	size_t *total_adds,
	size_t *total_dels,
806
	const git_patch *patch)
807 808 809 810 811 812
{
	size_t totals[3], idx;

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

	for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
813
		git_diff_line *line = git_array_get(patch->lines, idx);
814 815 816 817 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
		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;
}

845 846
int git_patch_get_hunk(
	const git_diff_hunk **out,
847
	size_t *lines_in_hunk,
848
	git_patch *patch,
849 850 851 852 853 854 855 856
	size_t hunk_idx)
{
	diff_patch_hunk *hunk;
	assert(patch);

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

	if (!hunk) {
857
		if (out) *out = NULL;
858 859 860 861
		if (lines_in_hunk) *lines_in_hunk = 0;
		return diff_error_outofrange("hunk");
	}

862
	if (out) *out = &hunk->hunk;
863 864 865 866
	if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
	return 0;
}

867
int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
868 869 870 871 872 873 874 875 876
{
	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;
}

877
int git_patch_get_line_in_hunk(
878
	const git_diff_line **out,
879
	git_patch *patch,
880 881 882 883
	size_t hunk_idx,
	size_t line_of_hunk)
{
	diff_patch_hunk *hunk;
884
	git_diff_line *line;
885 886 887 888

	assert(patch);

	if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
889 890
		if (out) *out = NULL;
		return diff_error_outofrange("hunk");
891 892 893 894 895
	}

	if (line_of_hunk >= hunk->line_count ||
		!(line = git_array_get(
			patch->lines, hunk->line_start + line_of_hunk))) {
896 897
		if (out) *out = NULL;
		return diff_error_outofrange("line");
898 899
	}

900
	if (out) *out = line;
901 902 903
	return 0;
}

904 905
size_t git_patch_size(
	git_patch *patch,
906 907 908
	int include_context,
	int include_hunk_headers,
	int include_file_headers)
909 910 911 912 913 914 915 916 917 918
{
	size_t out;

	assert(patch);

	out = patch->content_size;

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

919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
	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);
	}

934 935 936
	return out;
}

937
git_diff *git_patch__diff(git_patch *patch)
938 939 940 941
{
	return patch->diff;
}

942
git_diff_driver *git_patch__driver(git_patch *patch)
943 944 945 946 947
{
	/* ofile driver is representative for whole patch */
	return patch->ofile.driver;
}

948 949
void git_patch__old_data(
	char **ptr, size_t *len, git_patch *patch)
950 951 952 953 954
{
	*ptr = patch->ofile.map.data;
	*len = patch->ofile.map.len;
}

955 956
void git_patch__new_data(
	char **ptr, size_t *len, git_patch *patch)
957 958 959 960 961
{
	*ptr = patch->nfile.map.data;
	*len = patch->nfile.map.len;
}

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

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

976 977 978 979 980 981 982
	if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
		if (binary_cb)
			error = binary_cb(patch->delta, &patch->binary, payload);

		return error;
	}

983 984 985 986 987 988
	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);

989 990
		if (hunk_cb)
			error = hunk_cb(patch->delta, &h->hunk, payload);
991 992 993 994 995

		if (!line_cb)
			continue;

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

999
			error = line_cb(patch->delta, &h->hunk, l, payload);
1000 1001 1002 1003 1004 1005
		}
	}

	return error;
}

1006 1007 1008 1009 1010 1011

static int diff_patch_file_cb(
	const git_diff_delta *delta,
	float progress,
	void *payload)
{
1012
	GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
1013 1014 1015
	return 0;
}

1016 1017 1018 1019 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
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;
}

1046 1047
static int diff_patch_hunk_cb(
	const git_diff_delta *delta,
1048
	const git_diff_hunk *hunk_,
1049 1050
	void *payload)
{
1051
	git_patch *patch = payload;
1052 1053 1054 1055
	diff_patch_hunk *hunk;

	GIT_UNUSED(delta);

1056
	hunk = git_array_alloc(patch->hunks);
1057
	GITERR_CHECK_ALLOC(hunk);
1058

1059
	memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
1060

1061
	patch->header_size += hunk_->header_len;
1062

1063 1064 1065 1066 1067 1068 1069 1070
	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,
1071
	const git_diff_hunk *hunk_,
1072
	const git_diff_line *line_,
1073 1074
	void *payload)
{
1075
	git_patch *patch = payload;
1076
	diff_patch_hunk *hunk;
1077
	git_diff_line   *line;
1078 1079

	GIT_UNUSED(delta);
1080
	GIT_UNUSED(hunk_);
1081 1082

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

1085
	line = git_array_alloc(patch->lines);
1086
	GITERR_CHECK_ALLOC(line);
1087

1088
	memcpy(line, line_, sizeof(*line));
1089 1090 1091

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

1092
	patch->content_size += line->content_len;
1093

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

	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,
1112
	git_diff_binary_cb binary_cb,
1113
	git_diff_hunk_cb hunk_cb,
1114
	git_diff_line_cb data_cb,
1115 1116 1117 1118 1119 1120 1121
	void *payload)
{
	GIT_UNUSED(opts);

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

	out->file_cb = file_cb;
1122
	out->binary_cb = binary_cb;
1123 1124 1125 1126 1127
	out->hunk_cb = hunk_cb;
	out->data_cb = data_cb;
	out->payload = payload;
}

1128
static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
1129 1130
{
	diff_output_init(
1131 1132 1133 1134 1135 1136 1137
		out,
		NULL,
		diff_patch_file_cb,
		diff_patch_binary_cb,
		diff_patch_hunk_cb,
		diff_patch_line_cb,
		patch);
1138
}