patch_generate.c 23.2 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 9

#include "patch_generate.h"

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

20
static void diff_output_init(
21
	git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
22
	git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
23

24 25
static void diff_output_to_patch(
	git_patch_generated_output *, git_patch_generated *);
26

27
static void patch_generated_free(git_patch *p)
28
{
29
	git_patch_generated *patch = (git_patch_generated *)p;
30

31
	git_array_clear(patch->base.lines);
32 33 34 35 36
	git_array_clear(patch->base.hunks);

	git__free((char *)patch->base.binary.old_file.data);
	git__free((char *)patch->base.binary.new_file.data);

37 38 39 40 41 42 43 44 45 46 47
	git_diff_file_content__clear(&patch->ofile);
	git_diff_file_content__clear(&patch->nfile);

	git_diff_free(patch->diff); /* decrements refcount */
	patch->diff = NULL;

	git_pool_clear(&patch->flattened);

	git__free((char *)patch->base.diff_opts.old_prefix);
	git__free((char *)patch->base.diff_opts.new_prefix);

48
	if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
49 50 51
		git__free(patch);
}

52
static void patch_generated_update_binary(git_patch_generated *patch)
53 54
{
	if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
55 56
		return;

57 58
	if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
		(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
59
		patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
60

61
	else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
62 63
		patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
		patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
64

65
	else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
66 67
		(patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
		patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
68 69
}

70
static void patch_generated_init_common(git_patch_generated *patch)
71
{
72
	patch->base.free_fn = patch_generated_free;
73

74
	patch_generated_update_binary(patch);
75

76
	patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
77 78

	if (patch->diff)
79
		git_diff_addref(patch->diff);
80 81
}

82
static int patch_generated_normalize_options(
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	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;
}

108 109
static int patch_generated_init(
	git_patch_generated *patch, git_diff *diff, size_t delta_index)
110 111 112 113
{
	int error = 0;

	memset(patch, 0, sizeof(*patch));
114 115 116 117

	patch->diff = diff;
	patch->base.repo = diff->repo;
	patch->base.delta = git_vector_get(&diff->deltas, delta_index);
118 119
	patch->delta_index = delta_index;

120
	if ((error = patch_generated_normalize_options(
121
			&patch->base.diff_opts, &diff->opts)) < 0 ||
122
		(error = git_diff_file_content__init_from_diff(
123
			&patch->ofile, diff, patch->base.delta, true)) < 0 ||
124
		(error = git_diff_file_content__init_from_diff(
125
			&patch->nfile, diff, patch->base.delta, false)) < 0)
126 127
		return error;

128
	patch_generated_init_common(patch);
129 130 131 132

	return 0;
}

133 134
static int patch_generated_alloc_from_diff(
	git_patch_generated **out, git_diff *diff, size_t delta_index)
135 136
{
	int error;
137
	git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
138 139
	GITERR_CHECK_ALLOC(patch);

140 141
	if (!(error = patch_generated_init(patch, diff, delta_index))) {
		patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
142
		GIT_REFCOUNT_INC(&patch->base);
143 144 145 146 147 148 149 150 151
	} else {
		git__free(patch);
		patch = NULL;
	}

	*out = patch;
	return error;
}

152
GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
153
{
154
	if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
155 156 157 158 159
		return false;

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

160
static bool patch_generated_diffable(git_patch_generated *patch)
161 162 163
{
	size_t olen, nlen;

164
	if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
165 166 167 168 169 170
		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.
	 */
171 172
	if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
		(patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
		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));
}

189
static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
190 191 192 193
{
	int error = 0;
	bool incomplete_data;

194
	if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
195 196 197 198 199
		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
	 */
200
	if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
201
		output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
202 203 204
		return 0;

	incomplete_data =
205
		(((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
206
		  (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
207
		 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
208
		  (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
209

210 211 212 213 214 215 216 217
	if ((error = git_diff_file_content__load(
			&patch->ofile, &patch->base.diff_opts)) < 0 ||
		should_skip_binary(patch, patch->ofile.file))
		goto cleanup;
	if ((error = git_diff_file_content__load(
			&patch->nfile, &patch->base.diff_opts)) < 0 ||
		should_skip_binary(patch, patch->nfile.file))
		goto cleanup;
218

219 220 221
	/* if previously missing an oid, and now that we have it the two sides
	 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
	 */
222
	if (incomplete_data &&
223
		patch->ofile.file->mode == patch->nfile.file->mode &&
224
		patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
225
		git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
226 227
		patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
		patch->base.delta->status = GIT_DELTA_UNMODIFIED;
228 229

cleanup:
230
	patch_generated_update_binary(patch);
231 232

	if (!error) {
233 234
		if (patch_generated_diffable(patch))
			patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
235

236
		patch->flags |= GIT_PATCH_GENERATED_LOADED;
237 238 239 240 241
	}

	return error;
}

242 243
static int patch_generated_invoke_file_callback(
	git_patch_generated *patch, git_patch_generated_output *output)
244
{
245
	float progress = patch->diff ?
246 247
		((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;

248 249
	if (!output->file_cb)
		return 0;
250

251
	return giterr_set_after_callback_function(
252
		output->file_cb(patch->base.delta, progress, output->payload),
253
		"git_patch");
254 255
}

256 257 258 259 260 261 262 263 264 265 266
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;
267
	size_t delta_data_len = 0;
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
	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) {
284
		void *delta_data;
285

286 287 288 289 290 291
		error = git_delta(&delta_data, &delta_data_len,
			a_data, a_datalen,
			b_data, b_datalen,
			deflate.size);

		if (error == 0) {
292
			error = git_zstream_deflatebuf(
293
				&delta, delta_data, delta_data_len);
294 295

			git__free(delta_data);
296 297
		} else if (error == GIT_EBUFS) {
			error = 0;
298
		}
299 300 301

		if (error < 0)
			goto done;
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
	}

	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;
}

323
static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
324
{
325
	git_diff_binary binary = {0};
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;

332 333 334
	/* Only load contents if the user actually wants to diff
	 * binary files. */
	if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
335 336
		binary.contains_data = 1;

337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
		/* 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;
	}
352

353
	error = giterr_set_after_callback_function(
354
		output->binary_cb(patch->base.delta, &binary, output->payload),
355
		"git_patch");
356 357 358 359 360

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

	return error;
361 362
}

363 364 365
static int patch_generated_create(
	git_patch_generated *patch,
	git_patch_generated_output *output)
366 367 368
{
	int error = 0;

369
	if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
370 371
		return 0;

372 373
	/* 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)
374 375
		return 0;

376 377
	if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
		(error = patch_generated_load(patch, output)) < 0)
378 379
		return error;

380
	if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
381 382
		return 0;

383
	if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
384 385 386 387 388 389 390
		if (output->binary_cb)
			error = diff_binary(output, patch);
	}
	else {
		if (output->diff_cb)
			error = output->diff_cb(output, patch);
	}
391

392
	patch->flags |= GIT_PATCH_GENERATED_DIFFED;
393 394 395
	return error;
}

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

typedef struct {
405
	git_patch_generated patch;
406
	git_diff_delta delta;
407
	char paths[GIT_FLEX_ARRAY];
408
} patch_generated_with_delta;
409

410
static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
411 412
{
	int error = 0;
413
	git_patch_generated *patch = &pd->patch;
414 415
	bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
	bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
416

417
	pd->delta.status = has_new ?
418 419 420
		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);

421
	if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
422
		pd->delta.status = GIT_DELTA_UNMODIFIED;
423

424
	patch->base.delta = &pd->delta;
425

426
	patch_generated_init_common(patch);
427

428
	if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
429 430 431 432 433 434 435 436 437
		!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) {

		/* Even empty patches are flagged as binary, and even though
		 * there's no difference, we flag this as "containing data"
		 * (the data is known to be empty, as opposed to wholly unknown).
		 */
		if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
			patch->base.binary.contains_data = 1;

438
		return error;
439
	}
440

441
	error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
442 443

	if (!error)
444
		error = patch_generated_create(patch, (git_patch_generated_output *)xo);
445 446 447 448

	return error;
}

449 450
static int patch_generated_from_sources(
	patch_generated_with_delta *pd,
451
	git_xdiff_output *xo,
452 453
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
454
	const git_diff_options *opts)
455 456 457
{
	int error = 0;
	git_repository *repo =
458 459 460 461
		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;
462

463
	if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
464
		return error;
465 466

	if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
467 468
		void *tmp = lfile; lfile = rfile; rfile = tmp;
		tmp = ldata; ldata = rdata; rdata = tmp;
469 470
	}

471
	pd->patch.base.delta = &pd->delta;
472

473 474 475 476 477 478 479 480 481 482 483
	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;
484

485 486 487 488
	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)
489 490 491 492 493
		return error;

	return diff_single_generate(pd, xo);
}

494 495
static int patch_generated_with_delta_alloc(
	patch_generated_with_delta **out,
496 497 498
	const char **old_path,
	const char **new_path)
{
499
	patch_generated_with_delta *pd;
500 501
	size_t old_len = *old_path ? strlen(*old_path) : 0;
	size_t new_len = *new_path ? strlen(*new_path) : 0;
502
	size_t alloc_len;
503

504 505 506
	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);
507 508

	*out = pd = git__calloc(1, alloc_len);
509 510
	GITERR_CHECK_ALLOC(pd);

511
	pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527

	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;
}

528 529 530
static int diff_from_sources(
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
531 532
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
533
	git_diff_binary_cb binary_cb,
534
	git_diff_hunk_cb hunk_cb,
535
	git_diff_line_cb data_cb,
536 537 538
	void *payload)
{
	int error = 0;
539
	patch_generated_with_delta pd;
540 541 542 543
	git_xdiff_output xo;

	memset(&xo, 0, sizeof(xo));
	diff_output_init(
544
		&xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
545
	git_xdiff_init(&xo, opts);
546

547
	memset(&pd, 0, sizeof(pd));
548

549
	error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
550

551
	git_patch_free(&pd.patch.base);
552 553 554 555

	return error;
}

556
static int patch_from_sources(
557
	git_patch **out,
558 559
	git_diff_file_content_src *oldsrc,
	git_diff_file_content_src *newsrc,
560 561 562
	const git_diff_options *opts)
{
	int error = 0;
563
	patch_generated_with_delta *pd;
564 565 566 567 568
	git_xdiff_output xo;

	assert(out);
	*out = NULL;

569
	if ((error = patch_generated_with_delta_alloc(
570 571
			&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
		return error;
572 573

	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
574
	diff_output_to_patch(&xo.output, &pd->patch);
575 576
	git_xdiff_init(&xo, opts);

577
	if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
578
		*out = (git_patch *)pd;
579
	else
580
		git_patch_free((git_patch *)pd);
581 582 583 584

	return error;
}

585
int git_diff_blobs(
586
	const git_blob *old_blob,
587
	const char *old_path,
588 589 590 591
	const git_blob *new_blob,
	const char *new_path,
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
592
	git_diff_binary_cb binary_cb,
593 594 595
	git_diff_hunk_cb hunk_cb,
	git_diff_line_cb data_cb,
	void *payload)
596
{
597 598 599 600 601
	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(
602
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
603
}
604

605 606 607 608 609 610 611 612 613 614 615 616 617
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);
618 619 620 621
}

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

641 642
int git_patch_from_blob_and_buffer(
	git_patch **out,
643
	const git_blob *old_blob,
644
	const char *old_path,
645
	const void *buf,
646
	size_t buflen,
647
	const char *buf_path,
648 649
	const git_diff_options *opts)
{
650 651 652 653 654 655
	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);
}
656

657 658 659 660 661 662 663 664 665
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,
666
	git_diff_binary_cb binary_cb,
667 668 669 670 671 672 673 674 675
	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(
676
		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
677
}
678

679 680 681 682 683
int git_patch_from_buffers(
	git_patch **out,
	const void *old_buf,
	size_t old_len,
	const char *old_path,
684
	const void *new_buf,
685 686 687 688 689 690 691 692 693
	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);
694 695
}

696
int git_patch_generated_from_diff(
Russell Belfer committed
697
	git_patch **patch_ptr, git_diff *diff, size_t idx)
698 699 700 701
{
	int error = 0;
	git_xdiff_output xo;
	git_diff_delta *delta = NULL;
702
	git_patch_generated *patch = NULL;
703 704 705

	if (patch_ptr) *patch_ptr = NULL;

706
	if (diff_required(diff, "git_patch_from_diff") < 0)
707 708 709 710
		return -1;

	delta = git_vector_get(&diff->deltas, idx);
	if (!delta) {
711
		giterr_set(GITERR_INVALID, "index out of range for delta in diff");
712 713 714 715 716 717 718 719 720 721 722 723
		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;

724
	if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
725 726
		return error;

727
	memset(&xo, 0, sizeof(xo));
Russell Belfer committed
728
	diff_output_to_patch(&xo.output, patch);
729 730
	git_xdiff_init(&xo, &diff->opts);

731
	error = patch_generated_invoke_file_callback(patch, &xo.output);
732 733

	if (!error)
734
		error = patch_generated_create(patch, &xo.output);
735 736

	if (!error) {
737 738
		/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
		/* TODO: and unload the file content */
739 740 741
	}

	if (error || !patch_ptr)
742
		git_patch_free(&patch->base);
743
	else
744
		*patch_ptr = &patch->base;
745 746 747 748

	return error;
}

749
git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
750 751 752 753 754
{
	/* ofile driver is representative for whole patch */
	return patch->ofile.driver;
}

755 756
void git_patch_generated_old_data(
	char **ptr, size_t *len, git_patch_generated *patch)
757 758 759 760 761
{
	*ptr = patch->ofile.map.data;
	*len = patch->ofile.map.len;
}

762 763
void git_patch_generated_new_data(
	char **ptr, size_t *len, git_patch_generated *patch)
764 765 766 767 768
{
	*ptr = patch->nfile.map.data;
	*len = patch->nfile.map.len;
}

769
static int patch_generated_file_cb(
770 771 772 773
	const git_diff_delta *delta,
	float progress,
	void *payload)
{
774
	GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
775 776 777
	return 0;
}

778
static int patch_generated_binary_cb(
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
	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;
}

808
static int git_patch_hunk_cb(
809
	const git_diff_delta *delta,
810
	const git_diff_hunk *hunk_,
811 812
	void *payload)
{
813
	git_patch_generated  *patch = payload;
814
	git_patch_hunk *hunk;
815 816 817

	GIT_UNUSED(delta);

818
	hunk = git_array_alloc(patch->base.hunks);
819
	GITERR_CHECK_ALLOC(hunk);
820

821
	memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
822

823
	patch->base.header_size += hunk_->header_len;
824

825
	hunk->line_start = git_array_size(patch->base.lines);
826 827 828 829 830
	hunk->line_count = 0;

	return 0;
}

831
static int patch_generated_line_cb(
832
	const git_diff_delta *delta,
833
	const git_diff_hunk *hunk_,
834
	const git_diff_line *line_,
835 836
	void *payload)
{
837
	git_patch_generated  *patch = payload;
838
	git_patch_hunk *hunk;
839
	git_diff_line   *line;
840 841

	GIT_UNUSED(delta);
842
	GIT_UNUSED(hunk_);
843

844
	hunk = git_array_last(patch->base.hunks);
845
	assert(hunk); /* programmer error if no hunk is available */
846

847
	line = git_array_alloc(patch->base.lines);
848
	GITERR_CHECK_ALLOC(line);
849

850
	memcpy(line, line_, sizeof(*line));
851 852 853

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

854
	patch->base.content_size += line->content_len;
855

856 857
	if (line->origin == GIT_DIFF_LINE_ADDITION ||
		line->origin == GIT_DIFF_LINE_DELETION)
858
		patch->base.content_size += 1;
859
	else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
860 861
		patch->base.content_size += 1;
		patch->base.context_size += line->content_len + 1;
862
	} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
863
		patch->base.context_size += line->content_len;
864 865 866 867 868 869 870

	hunk->line_count++;

	return 0;
}

static void diff_output_init(
871
	git_patch_generated_output *out,
872 873
	const git_diff_options *opts,
	git_diff_file_cb file_cb,
874
	git_diff_binary_cb binary_cb,
875
	git_diff_hunk_cb hunk_cb,
876
	git_diff_line_cb data_cb,
877 878 879 880 881 882 883
	void *payload)
{
	GIT_UNUSED(opts);

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

	out->file_cb = file_cb;
884
	out->binary_cb = binary_cb;
885 886 887 888 889
	out->hunk_cb = hunk_cb;
	out->data_cb = data_cb;
	out->payload = payload;
}

890 891
static void diff_output_to_patch(
	git_patch_generated_output *out, git_patch_generated *patch)
892 893
{
	diff_output_init(
894 895
		out,
		NULL,
896 897
		patch_generated_file_cb,
		patch_generated_binary_cb,
898
		git_patch_hunk_cb,
899
		patch_generated_line_cb,
900
		patch);
901
}