diff_output.c 40 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7 8
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#include "common.h"
#include "git2/attr.h"
9
#include "git2/oid.h"
10
#include "git2/submodule.h"
11
#include "diff_output.h"
12 13
#include <ctype.h>
#include "fileops.h"
14
#include "filter.h"
15 16 17 18 19 20 21 22 23 24 25 26

static int read_next_int(const char **str, int *value)
{
	const char *scan = *str;
	int v = 0, digits = 0;
	/* find next digit */
	for (scan = *str; *scan && !isdigit(*scan); scan++);
	/* parse next number */
	for (; isdigit(*scan); scan++, digits++)
		v = (v * 10) + (*scan - '0');
	*str = scan;
	*value = v;
27
	return (digits > 0) ? 0 : -1;
28 29
}

Russell Belfer committed
30
static int parse_hunk_header(git_diff_range *range, const char *header)
31
{
Russell Belfer committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
	/* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
	if (*header != '@')
		return -1;
	if (read_next_int(&header, &range->old_start) < 0)
		return -1;
	if (*header == ',') {
		if (read_next_int(&header, &range->old_lines) < 0)
			return -1;
	} else
		range->old_lines = 1;
	if (read_next_int(&header, &range->new_start) < 0)
		return -1;
	if (*header == ',') {
		if (read_next_int(&header, &range->new_lines) < 0)
			return -1;
	} else
		range->new_lines = 1;
	if (range->old_start < 0 || range->new_start < 0)
		return -1;
51

Russell Belfer committed
52 53
	return 0;
}
54

55 56
#define KNOWN_BINARY_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
#define NOT_BINARY_FLAGS   (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
57

Russell Belfer committed
58 59
static int update_file_is_binary_by_attr(
	git_repository *repo, git_diff_file *file)
60 61
{
	const char *value;
Russell Belfer committed
62

Russell Belfer committed
63 64
	/* because of blob diffs, cannot assume path is set */
	if (!file->path || !strlen(file->path))
Russell Belfer committed
65 66
		return 0;

67
	if (git_attr_get(&value, repo, 0, file->path, "diff") < 0)
68 69
		return -1;

70
	if (GIT_ATTR_FALSE(value))
71
		file->flags |= GIT_DIFF_FILE_BINARY;
72
	else if (GIT_ATTR_TRUE(value))
73 74
		file->flags |= GIT_DIFF_FILE_NOT_BINARY;
	/* otherwise leave file->flags alone */
75 76

	return 0;
77 78
}

79
static void update_delta_is_binary(git_diff_delta *delta)
80
{
81 82
	if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 ||
		(delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)
83
		delta->binary = 1;
84 85 86

	else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
			 (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
87
		delta->binary = 0;
88

89 90 91
	/* otherwise leave delta->binary value untouched */
}

92 93
static int diff_delta_is_binary_by_attr(
	diff_context *ctxt, git_diff_patch *patch)
94
{
95
	int error = 0, mirror_new;
96
	git_diff_delta *delta = patch->delta;
97 98 99 100

	delta->binary = -1;

	/* make sure files are conceivably mmap-able */
101 102
	if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size ||
		(git_off_t)((size_t)delta->new_file.size) != delta->new_file.size)
103
	{
104 105
		delta->old_file.flags |= GIT_DIFF_FILE_BINARY;
		delta->new_file.flags |= GIT_DIFF_FILE_BINARY;
106
		delta->binary = 1;
107
		return 0;
108 109 110
	}

	/* check if user is forcing us to text diff these files */
111
	if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {
112 113
		delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
		delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
114
		delta->binary = 0;
115
		return 0;
116 117 118
	}

	/* check diff attribute +, -, or 0 */
Russell Belfer committed
119
	if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0)
120
		return -1;
121

122
	mirror_new = (delta->new_file.path == delta->old_file.path ||
123
		ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0);
124
	if (mirror_new)
125
		delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
126
	else
Russell Belfer committed
127
		error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);
128

129
	update_delta_is_binary(delta);
130 131 132 133

	return error;
}

134
static int diff_delta_is_binary_by_content(
135 136 137 138
	diff_context *ctxt,
	git_diff_delta *delta,
	git_diff_file *file,
	const git_map *map)
139
{
140
	const git_buf search = { map->data, 0, min(map->len, 4000) };
141

142 143 144
	GIT_UNUSED(ctxt);

	if ((file->flags & KNOWN_BINARY_FLAGS) == 0) {
145 146 147 148 149 150
		/* TODO: provide encoding / binary detection callbacks that can
		 * be UTF-8 aware, etc.  For now, instead of trying to be smart,
		 * let's just use the simple NUL-byte detection that core git uses.
		 */
		/* previously was: if (git_buf_text_is_binary(&search)) */
		if (git_buf_text_contains_nul(&search))
151
			file->flags |= GIT_DIFF_FILE_BINARY;
152
		else
153
			file->flags |= GIT_DIFF_FILE_NOT_BINARY;
154 155
	}

156
	update_delta_is_binary(delta);
157

158 159 160 161
	return 0;
}

static int diff_delta_is_binary_by_size(
162
	diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)
163 164 165
{
	git_off_t threshold = MAX_DIFF_FILESIZE;

166
	if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
167 168 169 170 171 172 173 174
		return 0;

	if (ctxt && ctxt->opts) {
		if (ctxt->opts->max_size < 0)
			return 0;

		if (ctxt->opts->max_size > 0)
			threshold = ctxt->opts->max_size;
175 176
	}

177 178
	if (file->size > threshold)
		file->flags |= GIT_DIFF_FILE_BINARY;
179

180
	update_delta_is_binary(delta);
181

182
	return 0;
183 184 185
}

static void setup_xdiff_options(
186
	const git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
187 188 189 190 191 192 193
{
	memset(cfg, 0, sizeof(xdemitconf_t));
	memset(param, 0, sizeof(xpparam_t));

	cfg->ctxlen =
		(!opts || !opts->context_lines) ? 3 : opts->context_lines;
	cfg->interhunkctxlen =
194
		(!opts) ? 0 : opts->interhunk_lines;
195 196 197 198 199 200 201 202 203 204 205 206

	if (!opts)
		return;

	if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE)
		param->flags |= XDF_WHITESPACE_FLAGS;
	if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
		param->flags |= XDF_IGNORE_WHITESPACE_CHANGE;
	if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
		param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
}

207

208
static int get_blob_content(
209 210
	diff_context *ctxt,
	git_diff_delta *delta,
211
	git_diff_file *file,
212 213 214
	git_map *map,
	git_blob **blob)
{
215
	int error;
216
	git_odb_object *odb_obj = NULL;
217 218

	if (git_oid_iszero(&file->oid))
219
		return 0;
220

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
	if (file->mode == GIT_FILEMODE_COMMIT)
	{
		char oidstr[GIT_OID_HEXSZ+1];
		git_buf content = GIT_BUF_INIT;

		git_oid_fmt(oidstr, &file->oid);
		oidstr[GIT_OID_HEXSZ] = 0;
		git_buf_printf(&content, "Subproject commit %s\n", oidstr );

		map->data = git_buf_detach(&content);
		map->len = strlen(map->data);

		file->flags |= GIT_DIFF_FILE_FREE_DATA;
		return 0;
	}

237 238 239 240
	if (!file->size) {
		git_odb *odb;
		size_t len;
		git_otype type;
241

242 243
		/* peek at object header to avoid loading if too large */
		if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 ||
244 245
			(error = git_odb__read_header_or_object(
				&odb_obj, &len, &type, odb, &file->oid)) < 0)
246
			return error;
247

248
		assert(type == GIT_OBJ_BLOB);
249 250

		file->size = len;
251 252 253
	}

	/* if blob is too large to diff, mark as binary */
254
	if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)
255
		return error;
256
	if (delta->binary == 1)
257
		return 0;
258

259 260 261 262 263 264 265 266
	if (odb_obj != NULL) {
		error = git_object__from_odb_object(
			(git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB);
		git_odb_object_free(odb_obj);
	} else
		error = git_blob_lookup(blob, ctxt->repo, &file->oid);

	if (error)
267
		return error;
268

269
	map->data = (void *)git_blob_rawcontent(*blob);
270
	map->len  = (size_t)git_blob_rawsize(*blob);
271

272
	return diff_delta_is_binary_by_content(ctxt, delta, file, map);
273 274
}

275
static int get_workdir_sm_content(
276
	diff_context *ctxt,
277 278 279
	git_diff_file *file,
	git_map *map)
{
280
	int error = 0;
281 282 283 284 285 286
	git_buf content = GIT_BUF_INIT;
	git_submodule* sm = NULL;
	unsigned int sm_status = 0;
	const char* sm_status_text = "";
	char oidstr[GIT_OID_HEXSZ+1];

287 288
	if ((error = git_submodule_lookup(&sm, ctxt->repo, file->path)) < 0 ||
		(error = git_submodule_status(&sm_status, sm)) < 0)
289
		return error;
290

291 292 293
	/* update OID if we didn't have it previously */
	if ((file->flags & GIT_DIFF_FILE_VALID_OID) == 0) {
		const git_oid* sm_head;
294

295 296
		if ((sm_head = git_submodule_wd_id(sm)) != NULL ||
			(sm_head = git_submodule_head_id(sm)) != NULL)
297 298 299 300
		{
			git_oid_cpy(&file->oid, sm_head);
			file->flags |= GIT_DIFF_FILE_VALID_OID;
		}
301
	}
302 303 304 305 306

	git_oid_fmt(oidstr, &file->oid);
	oidstr[GIT_OID_HEXSZ] = '\0';

	if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
307
		sm_status_text = "-dirty";
308

309 310
	git_buf_printf(&content, "Subproject commit %s%s\n",
				   oidstr, sm_status_text);
311

312 313
	map->data = git_buf_detach(&content);
	map->len = strlen(map->data);
314

315
	file->flags |= GIT_DIFF_FILE_FREE_DATA;
316

317 318
	return 0;
}
319

320 321 322 323 324 325 326 327 328
static int get_workdir_content(
	diff_context *ctxt,
	git_diff_delta *delta,
	git_diff_file *file,
	git_map *map)
{
	int error = 0;
	git_buf path = GIT_BUF_INIT;
	const char *wd = git_repository_workdir(ctxt->repo);
329

330
	if (S_ISGITLINK(file->mode))
331
		return get_workdir_sm_content(ctxt, file, map);
332

333 334 335
	if (S_ISDIR(file->mode))
		return 0;

336
	if (git_buf_joinpath(&path, wd, file->path) < 0)
337
		return -1;
338 339

	if (S_ISLNK(file->mode)) {
Russell Belfer committed
340
		ssize_t alloc_len, read_len;
341

342 343 344
		file->flags |= GIT_DIFF_FILE_FREE_DATA;
		file->flags |= GIT_DIFF_FILE_BINARY;

Russell Belfer committed
345 346 347 348 349 350
		/* link path on disk could be UTF-16, so prepare a buffer that is
		 * big enough to handle some UTF-8 data expansion
		 */
		alloc_len = (ssize_t)(file->size * 2) + 1;

		map->data = git__malloc(alloc_len);
351 352
		GITERR_CHECK_ALLOC(map->data);

Russell Belfer committed
353 354
		read_len = p_readlink(path.ptr, map->data, (int)alloc_len);
		if (read_len < 0) {
355 356
			giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path);
			error = -1;
357 358 359 360
			goto cleanup;
		}

		map->len = read_len;
361 362
	}
	else {
363 364 365 366 367 368 369 370 371 372 373
		git_file fd = git_futils_open_ro(path.ptr);
		git_vector filters = GIT_VECTOR_INIT;

		if (fd < 0) {
			error = fd;
			goto cleanup;
		}

		if (!file->size)
			file->size = git_futils_filesize(fd);

374 375
		if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
			delta->binary == 1)
376 377
			goto close_and_cleanup;

378 379
		error = git_filters_load(
			&filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB);
380 381 382 383
		if (error < 0)
			goto close_and_cleanup;

		if (error == 0) { /* note: git_filters_load returns filter count */
384 385 386
			if (!file->size)
				goto close_and_cleanup;

387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
			error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size);
			file->flags |= GIT_DIFF_FILE_UNMAP_DATA;
		} else {
			git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;

			if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) &&
				!(error = git_filters_apply(&filtered, &raw, &filters)))
			{
				map->len  = git_buf_len(&filtered);
				map->data = git_buf_detach(&filtered);

				file->flags |= GIT_DIFF_FILE_FREE_DATA;
			}

			git_buf_free(&raw);
			git_buf_free(&filtered);
		}

close_and_cleanup:
		git_filters_free(&filters);
		p_close(fd);
	}

	/* once data is loaded, update OID if we didn't have it previously */
	if (!error && (file->flags & GIT_DIFF_FILE_VALID_OID) == 0) {
		error = git_odb_hash(
			&file->oid, map->data, map->len, GIT_OBJ_BLOB);
		if (!error)
			file->flags |= GIT_DIFF_FILE_VALID_OID;
416
	}
417

418
	if (!error)
419
		error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
420

421
cleanup:
422
	git_buf_free(&path);
423 424 425 426 427 428 429 430 431 432
	return error;
}

static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
{
	if (blob != NULL)
		git_blob_free(blob);

	if (file->flags & GIT_DIFF_FILE_FREE_DATA) {
		git__free(map->data);
Russell Belfer committed
433 434
		map->data = "";
		map->len  = 0;
435 436 437 438
		file->flags &= ~GIT_DIFF_FILE_FREE_DATA;
	}
	else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) {
		git_futils_mmap_free(map);
Russell Belfer committed
439 440
		map->data = "";
		map->len  = 0;
441 442 443 444
		file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA;
	}
}

445 446 447 448 449

static void diff_context_init(
	diff_context *ctxt,
	git_diff_list *diff,
	git_repository *repo,
450
	const git_diff_options *opts,
Vicent Marti committed
451 452
	git_diff_file_cb file_cb,
	git_diff_hunk_cb hunk_cb,
453 454
	git_diff_data_cb data_cb,
	void *payload)
Russell Belfer committed
455
{
456
	memset(ctxt, 0, sizeof(diff_context));
Russell Belfer committed
457 458

	ctxt->repo = repo;
459
	ctxt->diff = diff;
Russell Belfer committed
460
	ctxt->opts = opts;
461 462 463
	ctxt->file_cb = file_cb;
	ctxt->hunk_cb = hunk_cb;
	ctxt->data_cb = data_cb;
464 465
	ctxt->payload = payload;
	ctxt->error = 0;
466

467
	setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
468 469
}

470
static int diff_delta_file_callback(
471
	diff_context *ctxt, git_diff_delta *delta, size_t idx)
472
{
473 474 475 476 477
	float progress;

	if (!ctxt->file_cb)
		return 0;

478
	progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;
479

480 481
	if (ctxt->file_cb(delta, progress, ctxt->payload) != 0)
		ctxt->error = GIT_EUSER;
482

483
	return ctxt->error;
Russell Belfer committed
484
}
485

486 487
static void diff_patch_init(
	diff_context *ctxt, git_diff_patch *patch)
Russell Belfer committed
488
{
489
	memset(patch, 0, sizeof(git_diff_patch));
490

491 492
	patch->diff = ctxt->diff;
	patch->ctxt = ctxt;
493

494 495 496 497 498 499
	if (patch->diff) {
		patch->old_src = patch->diff->old_src;
		patch->new_src = patch->diff->new_src;
	} else {
		patch->old_src = patch->new_src = GIT_ITERATOR_TREE;
	}
Russell Belfer committed
500
}
501

502 503
static git_diff_patch *diff_patch_alloc(
	diff_context *ctxt, git_diff_delta *delta)
Russell Belfer committed
504
{
505 506 507
	git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));
	if (!patch)
		return NULL;
508

509
	diff_patch_init(ctxt, patch);
510

511
	git_diff_list_addref(patch->diff);
512

513
	GIT_REFCOUNT_INC(patch);
514

515 516 517 518
	patch->delta = delta;
	patch->flags = GIT_DIFF_PATCH_ALLOCATED;

	return patch;
Russell Belfer committed
519
}
520

521 522
static int diff_patch_load(
	diff_context *ctxt, git_diff_patch *patch)
Russell Belfer committed
523 524
{
	int error = 0;
525
	git_diff_delta *delta = patch->delta;
526
	bool check_if_unmodified = false;
527

528
	if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
Russell Belfer committed
529
		return 0;
530

531
	error = diff_delta_is_binary_by_attr(ctxt, patch);
Russell Belfer committed
532

533 534 535
	patch->old_data.data = "";
	patch->old_data.len  = 0;
	patch->old_blob      = NULL;
Russell Belfer committed
536

537 538 539
	patch->new_data.data = "";
	patch->new_data.len  = 0;
	patch->new_blob      = NULL;
Russell Belfer committed
540

541 542
	if (delta->binary == 1)
		goto cleanup;
Russell Belfer committed
543

544 545 546 547 548
	if (!ctxt->hunk_cb &&
		!ctxt->data_cb &&
		(ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)
		goto cleanup;

549
	switch (delta->status) {
550 551 552 553 554 555 556 557
	case GIT_DELTA_ADDED:
		delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
		break;
	case GIT_DELTA_DELETED:
		delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA;
		break;
	case GIT_DELTA_MODIFIED:
		break;
558 559 560 561 562
	case GIT_DELTA_UNTRACKED:
		delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
		if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)
			delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA;
		break;
563 564 565 566
	default:
		delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA;
		delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
		break;
567
	}
568

569 570
#define CHECK_UNMODIFIED (GIT_DIFF_FILE_NO_DATA | GIT_DIFF_FILE_VALID_OID)

571
	check_if_unmodified =
572 573
		(delta->old_file.flags & CHECK_UNMODIFIED) == 0 &&
		(delta->new_file.flags & CHECK_UNMODIFIED) == 0;
574

575 576 577 578
	/* Always try to load workdir content first, since it may need to be
	 * filtered (and hence use 2x memory) and we want to minimize the max
	 * memory footprint during diff.
	 */
Russell Belfer committed
579

580
	if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
581
		patch->old_src == GIT_ITERATOR_WORKDIR) {
582
		if ((error = get_workdir_content(
583
				ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
584
			goto cleanup;
585
		if (delta->binary == 1)
586 587 588
			goto cleanup;
	}

589
	if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
590
		patch->new_src == GIT_ITERATOR_WORKDIR) {
591
		if ((error = get_workdir_content(
592
				ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
593
			goto cleanup;
594
		if (delta->binary == 1)
595 596 597
			goto cleanup;
	}

598
	if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
599
		patch->old_src != GIT_ITERATOR_WORKDIR) {
600
		if ((error = get_blob_content(
601 602
				ctxt, delta, &delta->old_file,
				&patch->old_data, &patch->old_blob)) < 0)
603 604 605 606
			goto cleanup;
		if (delta->binary == 1)
			goto cleanup;
	}
607

608
	if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
609
		patch->new_src != GIT_ITERATOR_WORKDIR) {
610
		if ((error = get_blob_content(
611 612
				ctxt, delta, &delta->new_file,
				&patch->new_data, &patch->new_blob)) < 0)
613 614 615 616
			goto cleanup;
		if (delta->binary == 1)
			goto cleanup;
	}
617 618 619 620 621 622 623 624 625 626 627 628

	/* if we did not previously have the definitive oid, we may have
	 * incorrect status and need to switch this to UNMODIFIED.
	 */
	if (check_if_unmodified &&
		delta->old_file.mode == delta->new_file.mode &&
		!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
	{
		delta->status = GIT_DELTA_UNMODIFIED;

		if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
			goto cleanup;
Russell Belfer committed
629 630
	}

631
cleanup:
632 633 634
	if (delta->binary == -1)
		update_delta_is_binary(delta);

635 636
	if (!error) {
		patch->flags |= GIT_DIFF_PATCH_LOADED;
Russell Belfer committed
637

638 639 640 641 642 643
		if (delta->binary != 1 &&
			delta->status != GIT_DELTA_UNMODIFIED &&
			(patch->old_data.len || patch->new_data.len) &&
			!git_oid_equal(&delta->old_file.oid, &delta->new_file.oid))
			patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
	}
Russell Belfer committed
644 645 646 647

	return error;
}

648
static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)
Russell Belfer committed
649
{
650 651
	git_diff_patch *patch = priv;
	diff_context   *ctxt  = patch->ctxt;
Russell Belfer committed
652 653

	if (len == 1) {
654 655 656
		ctxt->error = parse_hunk_header(&ctxt->range, bufs[0].ptr);
		if (ctxt->error < 0)
			return ctxt->error;
Russell Belfer committed
657

658
		if (ctxt->hunk_cb != NULL &&
659 660 661
			ctxt->hunk_cb(patch->delta, &ctxt->range,
				bufs[0].ptr, bufs[0].size, ctxt->payload))
			ctxt->error = GIT_EUSER;
Russell Belfer committed
662
	}
663

Russell Belfer committed
664 665 666 667 668 669 670
	if (len == 2 || len == 3) {
		/* expect " "/"-"/"+", then data */
		char origin =
			(*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
			(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
			GIT_DIFF_LINE_CONTEXT;

671
		if (ctxt->data_cb != NULL &&
672 673 674
			ctxt->data_cb(patch->delta, &ctxt->range,
				origin, bufs[1].ptr, bufs[1].size, ctxt->payload))
			ctxt->error = GIT_EUSER;
Russell Belfer committed
675 676
	}

677
	if (len == 3 && !ctxt->error) {
678 679 680 681
		/* If we have a '+' and a third buf, then we have added a line
		 * without a newline and the old code had one, so DEL_EOFNL.
		 * If we have a '-' and a third buf, then we have removed a line
		 * with out a newline but added a blank line, so ADD_EOFNL.
682
		 */
Russell Belfer committed
683 684 685 686 687
		char origin =
			(*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
			(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
			GIT_DIFF_LINE_CONTEXT;

688
		if (ctxt->data_cb != NULL &&
689 690 691
			ctxt->data_cb(patch->delta, &ctxt->range,
				origin, bufs[2].ptr, bufs[2].size, ctxt->payload))
			ctxt->error = GIT_EUSER;
Russell Belfer committed
692 693
	}

694
	return ctxt->error;
Russell Belfer committed
695 696
}

697 698
static int diff_patch_generate(
	diff_context *ctxt, git_diff_patch *patch)
Russell Belfer committed
699 700 701 702 703
{
	int error = 0;
	xdemitcb_t xdiff_callback;
	mmfile_t old_xdiff_data, new_xdiff_data;

704
	if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
Russell Belfer committed
705 706
		return 0;

707 708 709
	if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
		if ((error = diff_patch_load(ctxt, patch)) < 0)
			return error;
Russell Belfer committed
710

711
	if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
Russell Belfer committed
712 713
		return 0;

714 715 716 717
	if (!ctxt->file_cb && !ctxt->hunk_cb)
		return 0;

	patch->ctxt = ctxt;
Russell Belfer committed
718 719

	memset(&xdiff_callback, 0, sizeof(xdiff_callback));
720 721
	xdiff_callback.outf = diff_patch_cb;
	xdiff_callback.priv = patch;
Russell Belfer committed
722

723 724 725 726
	old_xdiff_data.ptr  = patch->old_data.data;
	old_xdiff_data.size = patch->old_data.len;
	new_xdiff_data.ptr  = patch->new_data.data;
	new_xdiff_data.size = patch->new_data.len;
Russell Belfer committed
727 728 729 730

	xdl_diff(&old_xdiff_data, &new_xdiff_data,
		&ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);

731
	error = ctxt->error;
Russell Belfer committed
732

733 734
	if (!error)
		patch->flags |= GIT_DIFF_PATCH_DIFFED;
Russell Belfer committed
735 736 737 738

	return error;
}

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
static void diff_patch_unload(git_diff_patch *patch)
{
	if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {
		patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);

		patch->hunks_size = 0;
		patch->lines_size = 0;
	}

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

		release_content(
			&patch->delta->old_file, &patch->old_data, patch->old_blob);
		release_content(
			&patch->delta->new_file, &patch->new_data, patch->new_blob);
	}
}

static void diff_patch_free(git_diff_patch *patch)
{
	diff_patch_unload(patch);

	git__free(patch->lines);
	patch->lines = NULL;
	patch->lines_asize = 0;

	git__free(patch->hunks);
	patch->hunks = NULL;
	patch->hunks_asize = 0;

	if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))
		return;

	patch->flags = 0;

	git_diff_list_free(patch->diff); /* decrements refcount */

	git__free(patch);
}

#define MAX_HUNK_STEP 128
#define MIN_HUNK_STEP 8
#define MAX_LINE_STEP 256
#define MIN_LINE_STEP 8

static int diff_patch_hunk_cb(
786 787
	const git_diff_delta *delta,
	const git_diff_range *range,
788
	const char *header,
789 790
	size_t header_len,
	void *payload)
791
{
792
	git_diff_patch *patch = payload;
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
	diff_patch_hunk *hunk;

	GIT_UNUSED(delta);

	if (patch->hunks_size >= patch->hunks_asize) {
		size_t new_size;
		diff_patch_hunk *new_hunks;

		if (patch->hunks_asize > MAX_HUNK_STEP)
			new_size = patch->hunks_asize + MAX_HUNK_STEP;
		else
			new_size = patch->hunks_asize * 2;
		if (new_size < MIN_HUNK_STEP)
			new_size = MIN_HUNK_STEP;

		new_hunks = git__realloc(
			patch->hunks, new_size * sizeof(diff_patch_hunk));
		if (!new_hunks)
			return -1;

		patch->hunks = new_hunks;
		patch->hunks_asize = new_size;
	}

	hunk = &patch->hunks[patch->hunks_size++];

	memcpy(&hunk->range, range, sizeof(hunk->range));

	assert(header_len + 1 < sizeof(hunk->header));
	memcpy(&hunk->header, header, header_len);
	hunk->header[header_len] = '\0';
	hunk->header_len = header_len;

	hunk->line_start = patch->lines_size;
	hunk->line_count = 0;

829 830 831
	patch->oldno = range->old_start;
	patch->newno = range->new_start;

832 833 834 835
	return 0;
}

static int diff_patch_line_cb(
836 837
	const git_diff_delta *delta,
	const git_diff_range *range,
838 839
	char line_origin,
	const char *content,
840 841
	size_t content_len,
	void *payload)
842
{
843
	git_diff_patch *patch = payload;
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
	diff_patch_hunk *hunk;
	diff_patch_line *last, *line;

	GIT_UNUSED(delta);
	GIT_UNUSED(range);

	assert(patch->hunks_size > 0);
	assert(patch->hunks != NULL);

	hunk = &patch->hunks[patch->hunks_size - 1];

	if (patch->lines_size >= patch->lines_asize) {
		size_t new_size;
		diff_patch_line *new_lines;

		if (patch->lines_asize > MAX_LINE_STEP)
			new_size = patch->lines_asize + MAX_LINE_STEP;
		else
			new_size = patch->lines_asize * 2;
		if (new_size < MIN_LINE_STEP)
			new_size = MIN_LINE_STEP;

		new_lines = git__realloc(
			patch->lines, new_size * sizeof(diff_patch_line));
		if (!new_lines)
			return -1;

		patch->lines = new_lines;
		patch->lines_asize = new_size;
	}

	last = (patch->lines_size > 0) ?
		&patch->lines[patch->lines_size - 1] : NULL;
	line = &patch->lines[patch->lines_size++];

	line->ptr = content;
	line->len = content_len;
	line->origin = line_origin;

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

	for (line->lines = 0; content_len > 0; --content_len) {
		if (*content++ == '\n')
			++line->lines;
	}

890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
	switch (line_origin) {
	case GIT_DIFF_LINE_ADDITION:
		line->oldno = -1;
		line->newno = patch->newno;
		patch->newno += line->lines;
		break;
	case GIT_DIFF_LINE_DELETION:
		line->oldno = patch->oldno;
		line->newno = -1;
		patch->oldno += line->lines;
		break;
	default:
		line->oldno = patch->oldno;
		line->newno = patch->newno;
		patch->oldno += line->lines;
		patch->newno += line->lines;
		break;
907 908
	}

909 910
	hunk->line_count++;

911 912 913 914
	return 0;
}


Russell Belfer committed
915 916
int git_diff_foreach(
	git_diff_list *diff,
Vicent Marti committed
917 918
	git_diff_file_cb file_cb,
	git_diff_hunk_cb hunk_cb,
919 920
	git_diff_data_cb data_cb,
	void *payload)
Russell Belfer committed
921
{
922
	int error = 0;
923
	diff_context ctxt;
Russell Belfer committed
924
	size_t idx;
925
	git_diff_patch patch;
Russell Belfer committed
926

927 928
	diff_context_init(
		&ctxt, diff, diff->repo, &diff->opts,
929
		file_cb, hunk_cb, data_cb, payload);
Russell Belfer committed
930

931
	diff_patch_init(&ctxt, &patch);
932

933
	git_vector_foreach(&diff->deltas, idx, patch.delta) {
Russell Belfer committed
934

935
		/* check flags against patch status */
936
		if (git_diff_delta__should_skip(ctxt.opts, patch.delta))
937
			continue;
938

939
		if (!(error = diff_patch_load(&ctxt, &patch))) {
940

941 942 943 944 945 946 947 948 949
			/* invoke file callback */
			error = diff_delta_file_callback(&ctxt, patch.delta, idx);

			/* generate diffs and invoke hunk and line callbacks */
			if (!error)
				error = diff_patch_generate(&ctxt, &patch);

			diff_patch_unload(&patch);
		}
950

951
		if (error < 0)
952 953 954
			break;
	}

Russell Belfer committed
955
	if (error == GIT_EUSER)
956
		giterr_clear(); /* don't let error message leak */
Russell Belfer committed
957

958 959 960
	return error;
}

961

962 963
typedef struct {
	git_diff_list *diff;
Vicent Marti committed
964
	git_diff_data_cb print_cb;
965
	void *payload;
966 967 968 969 970 971 972
	git_buf *buf;
} diff_print_info;

static char pick_suffix(int mode)
{
	if (S_ISDIR(mode))
		return '/';
973
	else if (mode & 0100) //-V536
974 975 976 977 978 979
		/* in git, modes are very regular, so we must have 0100755 mode */
		return '*';
	else
		return ' ';
}

980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
char git_diff_status_char(git_delta_t status)
{
	char code;

	switch (status) {
	case GIT_DELTA_ADDED:     code = 'A'; break;
	case GIT_DELTA_DELETED:   code = 'D'; break;
	case GIT_DELTA_MODIFIED:  code = 'M'; break;
	case GIT_DELTA_RENAMED:   code = 'R'; break;
	case GIT_DELTA_COPIED:    code = 'C'; break;
	case GIT_DELTA_IGNORED:   code = 'I'; break;
	case GIT_DELTA_UNTRACKED: code = '?'; break;
	default:                  code = ' '; break;
	}

	return code;
}

998
static int print_compact(
999
	const git_diff_delta *delta, float progress, void *data)
1000 1001
{
	diff_print_info *pi = data;
1002
	char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
1003

1004
	GIT_UNUSED(progress);
1005

1006
	if (code == ' ')
1007
		return 0;
1008

1009 1010
	old_suffix = pick_suffix(delta->old_file.mode);
	new_suffix = pick_suffix(delta->new_file.mode);
1011 1012 1013

	git_buf_clear(pi->buf);

1014
	if (delta->old_file.path != delta->new_file.path &&
1015
		pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)
1016
		git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
1017 1018 1019
			delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
	else if (delta->old_file.mode != delta->new_file.mode &&
		delta->old_file.mode != 0 && delta->new_file.mode != 0)
1020
		git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
1021
			delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);
1022
	else if (old_suffix != ' ')
1023
		git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
1024
	else
1025
		git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path);
1026

1027 1028
	if (git_buf_oom(pi->buf))
		return -1;
1029

1030 1031
	if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
			git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
Russell Belfer committed
1032 1033
	{
		giterr_clear();
1034
		return GIT_EUSER;
Russell Belfer committed
1035
	}
1036 1037

	return 0;
1038 1039 1040 1041
}

int git_diff_print_compact(
	git_diff_list *diff,
1042 1043
	git_diff_data_cb print_cb,
	void *payload)
1044 1045 1046 1047 1048 1049 1050
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;

	pi.diff     = diff;
	pi.print_cb = print_cb;
1051
	pi.payload  = payload;
1052 1053
	pi.buf      = &buf;

1054
	error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
1055 1056 1057 1058 1059 1060

	git_buf_free(&buf);

	return error;
}

1061
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
1062 1063 1064 1065
{
	char start_oid[8], end_oid[8];

	/* TODO: Determine a good actual OID range to print */
1066 1067
	git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid);
	git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid);
1068 1069

	/* TODO: Match git diff more closely */
1070
	if (delta->old_file.mode == delta->new_file.mode) {
1071
		git_buf_printf(pi->buf, "index %s..%s %o\n",
1072
			start_oid, end_oid, delta->old_file.mode);
1073
	} else {
1074 1075 1076 1077
		if (delta->old_file.mode == 0) {
			git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode);
		} else if (delta->new_file.mode == 0) {
			git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode);
1078
		} else {
1079 1080
			git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode);
			git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode);
1081 1082 1083 1084
		}
		git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);
	}

1085
	if (git_buf_oom(pi->buf))
1086
		return -1;
1087 1088

	return 0;
1089 1090
}

1091
static int print_patch_file(
1092
	const git_diff_delta *delta, float progress, void *data)
1093 1094
{
	diff_print_info *pi = data;
1095 1096 1097 1098
	const char *oldpfx = pi->diff->opts.old_prefix;
	const char *oldpath = delta->old_file.path;
	const char *newpfx = pi->diff->opts.new_prefix;
	const char *newpath = delta->new_file.path;
1099

1100
	GIT_UNUSED(progress);
1101

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

1105
	if (!oldpfx)
1106
		oldpfx = DIFF_OLD_PREFIX_DEFAULT;
1107 1108

	if (!newpfx)
1109
		newpfx = DIFF_NEW_PREFIX_DEFAULT;
1110

1111
	git_buf_clear(pi->buf);
1112
	git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
1113 1114 1115

	if (print_oid_range(pi, delta) < 0)
		return -1;
1116

1117
	if (git_oid_iszero(&delta->old_file.oid)) {
1118 1119 1120
		oldpfx = "";
		oldpath = "/dev/null";
	}
1121
	if (git_oid_iszero(&delta->new_file.oid)) {
1122 1123
		newpfx = "";
		newpath = "/dev/null";
1124 1125 1126 1127 1128 1129 1130
	}

	if (delta->binary != 1) {
		git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
		git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
	}

1131
	if (git_buf_oom(pi->buf))
1132
		return -1;
1133

1134 1135
	if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
			git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
Russell Belfer committed
1136 1137
	{
		giterr_clear();
1138
		return GIT_EUSER;
Russell Belfer committed
1139
	}
1140

Sascha Cunz committed
1141 1142
	if (delta->binary != 1)
		return 0;
1143 1144 1145 1146 1147

	git_buf_clear(pi->buf);
	git_buf_printf(
		pi->buf, "Binary files %s%s and %s%s differ\n",
		oldpfx, oldpath, newpfx, newpath);
1148
	if (git_buf_oom(pi->buf))
1149
		return -1;
1150

1151 1152
	if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
			git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
Russell Belfer committed
1153 1154
	{
		giterr_clear();
1155
		return GIT_EUSER;
Russell Belfer committed
1156
	}
1157 1158

	return 0;
1159 1160 1161
}

static int print_patch_hunk(
1162 1163
	const git_diff_delta *d,
	const git_diff_range *r,
1164
	const char *header,
1165 1166
	size_t header_len,
	void *data)
1167 1168 1169
{
	diff_print_info *pi = data;

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

1173
	git_buf_clear(pi->buf);
1174 1175
	if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
		return -1;
1176

1177 1178
	if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,
			git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
Russell Belfer committed
1179 1180
	{
		giterr_clear();
1181
		return GIT_EUSER;
Russell Belfer committed
1182
	}
1183 1184

	return 0;
1185 1186 1187
}

static int print_patch_line(
1188 1189
	const git_diff_delta *delta,
	const git_diff_range *range,
1190 1191
	char line_origin, /* GIT_DIFF_LINE value from above */
	const char *content,
1192 1193
	size_t content_len,
	void *data)
1194 1195 1196
{
	diff_print_info *pi = data;

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

1200 1201 1202 1203 1204 1205 1206 1207 1208
	git_buf_clear(pi->buf);

	if (line_origin == GIT_DIFF_LINE_ADDITION ||
		line_origin == GIT_DIFF_LINE_DELETION ||
		line_origin == GIT_DIFF_LINE_CONTEXT)
		git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);
	else if (content_len > 0)
		git_buf_printf(pi->buf, "%.*s", (int)content_len, content);

1209
	if (git_buf_oom(pi->buf))
1210
		return -1;
1211

1212 1213
	if (pi->print_cb(delta, range, line_origin,
			git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
Russell Belfer committed
1214 1215
	{
		giterr_clear();
1216
		return GIT_EUSER;
Russell Belfer committed
1217
	}
1218 1219

	return 0;
1220 1221 1222 1223
}

int git_diff_print_patch(
	git_diff_list *diff,
1224 1225
	git_diff_data_cb print_cb,
	void *payload)
1226 1227 1228 1229 1230 1231 1232
{
	int error;
	git_buf buf = GIT_BUF_INIT;
	diff_print_info pi;

	pi.diff     = diff;
	pi.print_cb = print_cb;
1233
	pi.payload  = payload;
1234 1235 1236
	pi.buf      = &buf;

	error = git_diff_foreach(
1237
		diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
1238 1239 1240 1241 1242 1243

	git_buf_free(&buf);

	return error;
}

Russell Belfer committed
1244
static void set_data_from_blob(
1245
	const git_blob *blob, git_map *map, git_diff_file *file)
Russell Belfer committed
1246 1247
{
	if (blob) {
1248
		file->size = git_blob_rawsize(blob);
Russell Belfer committed
1249
		git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
1250
		file->mode = 0644;
1251 1252 1253

		map->len   = (size_t)file->size;
		map->data  = (char *)git_blob_rawcontent(blob);
Russell Belfer committed
1254
	} else {
1255
		file->size = 0;
1256
		file->flags |= GIT_DIFF_FILE_NO_DATA;
1257 1258 1259

		map->len   = 0;
		map->data  = "";
Russell Belfer committed
1260 1261 1262
	}
}

1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287
static void set_data_from_buffer(
	const char *buffer, size_t buffer_len, git_map *map, git_diff_file *file)
{
	file->size = (git_off_t)buffer_len;
	file->mode = 0644;

	if (!buffer)
		file->flags |= GIT_DIFF_FILE_NO_DATA;
	else
		git_odb_hash(&file->oid, buffer, buffer_len, GIT_OBJ_BLOB);

	map->len   = buffer_len;
	map->data  = (char *)buffer;
}

typedef struct {
	diff_context   ctxt;
	git_diff_delta delta;
	git_diff_patch patch;
} diff_single_data;

static int diff_single_init(
	diff_single_data *data,
	git_repository *repo,
	const git_diff_options *opts,
Vicent Marti committed
1288 1289
	git_diff_file_cb file_cb,
	git_diff_hunk_cb hunk_cb,
1290 1291
	git_diff_data_cb data_cb,
	void *payload)
1292
{
1293
	GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
1294

1295
	memset(data, 0, sizeof(*data));
Russell Belfer committed
1296

1297
	diff_context_init(
1298
		&data->ctxt, NULL, repo, opts, file_cb, hunk_cb, data_cb, payload);
1299

1300
	diff_patch_init(&data->ctxt, &data->patch);
1301

1302 1303
	return 0;
}
Russell Belfer committed
1304

1305 1306 1307 1308 1309 1310
static int diff_single_apply(diff_single_data *data)
{
	int error;
	git_diff_delta *delta = &data->delta;
	bool has_old = ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0);
	bool has_new = ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0);
Russell Belfer committed
1311

1312
	/* finish setting up fake git_diff_delta record and loaded data */
Russell Belfer committed
1313

1314 1315
	data->patch.delta = delta;
	delta->binary = -1;
1316

1317 1318 1319
	delta->status = has_new ?
		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
1320

1321 1322
	if (git_oid_cmp(&delta->new_file.oid, &delta->old_file.oid) == 0)
		delta->status = GIT_DELTA_UNMODIFIED;
Russell Belfer committed
1323

1324
	if ((error = diff_delta_is_binary_by_content(
1325
			&data->ctxt, delta, &delta->old_file, &data->patch.old_data)) < 0 ||
1326
		(error = diff_delta_is_binary_by_content(
1327
			&data->ctxt, delta, &delta->new_file, &data->patch.new_data)) < 0)
1328 1329
		goto cleanup;

1330 1331 1332 1333
	data->patch.flags |= GIT_DIFF_PATCH_LOADED;

	if (delta->binary != 1 && delta->status != GIT_DELTA_UNMODIFIED)
		data->patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
1334 1335 1336

	/* do diffs */

1337 1338
	if (!(error = diff_delta_file_callback(&data->ctxt, delta, 1)))
		error = diff_patch_generate(&data->ctxt, &data->patch);
1339 1340 1341 1342 1343

cleanup:
	if (error == GIT_EUSER)
		giterr_clear();

1344
	diff_patch_unload(&data->patch);
1345

1346
	return error;
1347 1348
}

1349 1350 1351
int git_diff_blobs(
	const git_blob *old_blob,
	const git_blob *new_blob,
1352 1353 1354 1355 1356 1357 1358
	const git_diff_options *options,
	git_diff_file_cb file_cb,
	git_diff_hunk_cb hunk_cb,
	git_diff_data_cb data_cb,
	void *payload)
{
	int error;
1359 1360 1361 1362
	diff_single_data d;
	git_repository *repo =
		new_blob ? git_object_owner((const git_object *)new_blob) :
		old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
1363

1364 1365 1366
	if ((error = diff_single_init(
			&d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
		return error;
1367

1368 1369 1370 1371 1372
	if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
		const git_blob *swap = old_blob;
		old_blob = new_blob;
		new_blob = swap;
	}
1373

1374 1375
	set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
	set_data_from_blob(new_blob, &d.patch.new_data, &d.delta.new_file);
1376

1377 1378
	return diff_single_apply(&d);
}
1379

1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393
int git_diff_blob_to_buffer(
	const git_blob *old_blob,
	const char *buf,
	size_t buflen,
	const git_diff_options *options,
	git_diff_file_cb file_cb,
	git_diff_hunk_cb hunk_cb,
	git_diff_data_cb data_cb,
	void *payload)
{
	int error;
	diff_single_data d;
	git_repository *repo =
		old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
1394

1395 1396 1397
	if ((error = diff_single_init(
			&d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
		return error;
1398

1399 1400 1401
	if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
		set_data_from_buffer(buf, buflen, &d.patch.old_data, &d.delta.old_file);
		set_data_from_blob(old_blob, &d.patch.new_data, &d.delta.new_file);
1402
	} else {
1403 1404
		set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
		set_data_from_buffer(buf, buflen, &d.patch.new_data, &d.delta.new_file);
1405 1406
	}

1407
	return diff_single_apply(&d);
Russell Belfer committed
1408 1409
}

1410
size_t git_diff_num_deltas(git_diff_list *diff)
Russell Belfer committed
1411
{
1412 1413
	assert(diff);
	return (size_t)diff->deltas.length;
Russell Belfer committed
1414
}
1415

1416
size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
Russell Belfer committed
1417
{
1418 1419
	size_t i, count = 0;
	git_diff_delta *delta;
Russell Belfer committed
1420

1421
	assert(diff);
Russell Belfer committed
1422

1423 1424
	git_vector_foreach(&diff->deltas, i, delta) {
		count += (delta->status == type);
Russell Belfer committed
1425 1426
	}

1427
	return count;
Russell Belfer committed
1428 1429
}

1430 1431
int git_diff_get_patch(
	git_diff_patch **patch_ptr,
1432
	const git_diff_delta **delta_ptr,
1433 1434
	git_diff_list *diff,
	size_t idx)
Russell Belfer committed
1435 1436
{
	int error;
1437 1438 1439
	diff_context ctxt;
	git_diff_delta *delta;
	git_diff_patch *patch;
1440

1441 1442
	if (patch_ptr)
		*patch_ptr = NULL;
Russell Belfer committed
1443

1444 1445 1446 1447 1448 1449
	delta = git_vector_get(&diff->deltas, idx);
	if (!delta) {
		if (delta_ptr)
			*delta_ptr = NULL;
		giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
		return GIT_ENOTFOUND;
Russell Belfer committed
1450 1451
	}

1452 1453
	if (delta_ptr)
		*delta_ptr = delta;
Russell Belfer committed
1454

1455 1456 1457
	if (!patch_ptr &&
		(delta->binary != -1 ||
		 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
1458
		return 0;
Russell Belfer committed
1459

1460 1461
	diff_context_init(
		&ctxt, diff, diff->repo, &diff->opts,
1462
		NULL, diff_patch_hunk_cb, diff_patch_line_cb, NULL);
Russell Belfer committed
1463

1464
	if (git_diff_delta__should_skip(ctxt.opts, delta))
1465
		return 0;
Russell Belfer committed
1466

1467 1468 1469
	patch = diff_patch_alloc(&ctxt, delta);
	if (!patch)
		return -1;
Russell Belfer committed
1470

1471
	if (!(error = diff_patch_load(&ctxt, patch))) {
1472
		ctxt.payload = patch;
Russell Belfer committed
1473

1474
		error = diff_patch_generate(&ctxt, patch);
Russell Belfer committed
1475

1476
		if (error == GIT_EUSER)
1477
			error = ctxt.error;
1478
	}
Russell Belfer committed
1479

1480 1481 1482 1483
	if (error)
		git_diff_patch_free(patch);
	else if (patch_ptr)
		*patch_ptr = patch;
1484

1485
	return error;
Russell Belfer committed
1486 1487
}

1488
void git_diff_patch_free(git_diff_patch *patch)
Russell Belfer committed
1489
{
1490 1491
	if (patch)
		GIT_REFCOUNT_DEC(patch, diff_patch_free);
Russell Belfer committed
1492 1493
}

1494
const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
Russell Belfer committed
1495
{
1496 1497
	assert(patch);
	return patch->delta;
Russell Belfer committed
1498 1499
}

1500
size_t git_diff_patch_num_hunks(git_diff_patch *patch)
Russell Belfer committed
1501
{
1502 1503
	assert(patch);
	return patch->hunks_size;
Russell Belfer committed
1504 1505
}

1506
int git_diff_patch_get_hunk(
1507
	const git_diff_range **range,
Russell Belfer committed
1508 1509
	const char **header,
	size_t *header_len,
1510 1511 1512
	size_t *lines_in_hunk,
	git_diff_patch *patch,
	size_t hunk_idx)
Russell Belfer committed
1513
{
1514
	diff_patch_hunk *hunk;
Russell Belfer committed
1515

1516
	assert(patch);
Russell Belfer committed
1517

1518 1519 1520 1521 1522 1523
	if (hunk_idx >= patch->hunks_size) {
		if (range) *range = NULL;
		if (header) *header = NULL;
		if (header_len) *header_len = 0;
		if (lines_in_hunk) *lines_in_hunk = 0;
		return GIT_ENOTFOUND;
Russell Belfer committed
1524 1525
	}

1526
	hunk = &patch->hunks[hunk_idx];
Russell Belfer committed
1527

1528 1529 1530 1531
	if (range) *range = &hunk->range;
	if (header) *header = hunk->header;
	if (header_len) *header_len = hunk->header_len;
	if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
Russell Belfer committed
1532

1533 1534
	return 0;
}
1535

1536 1537 1538 1539 1540
int git_diff_patch_num_lines_in_hunk(
	git_diff_patch *patch,
	size_t hunk_idx)
{
	assert(patch);
1541

1542 1543 1544
	if (hunk_idx >= patch->hunks_size)
		return GIT_ENOTFOUND;
	else
Russell Belfer committed
1545
		return (int)patch->hunks[hunk_idx].line_count;
Russell Belfer committed
1546 1547
}

1548 1549 1550
int git_diff_patch_get_line_in_hunk(
	char *line_origin,
	const char **content,
Russell Belfer committed
1551
	size_t *content_len,
1552 1553 1554 1555 1556
	int *old_lineno,
	int *new_lineno,
	git_diff_patch *patch,
	size_t hunk_idx,
	size_t line_of_hunk)
Russell Belfer committed
1557
{
1558 1559
	diff_patch_hunk *hunk;
	diff_patch_line *line;
Russell Belfer committed
1560

1561
	assert(patch);
Russell Belfer committed
1562

1563 1564 1565
	if (hunk_idx >= patch->hunks_size)
		goto notfound;
	hunk = &patch->hunks[hunk_idx];
Russell Belfer committed
1566

1567 1568
	if (line_of_hunk >= hunk->line_count)
		goto notfound;
Russell Belfer committed
1569

1570
	line = &patch->lines[hunk->line_start + line_of_hunk];
Russell Belfer committed
1571

1572 1573 1574 1575 1576
	if (line_origin) *line_origin = line->origin;
	if (content) *content = line->ptr;
	if (content_len) *content_len = line->len;
	if (old_lineno) *old_lineno = line->oldno;
	if (new_lineno) *new_lineno = line->newno;
Russell Belfer committed
1577

1578 1579 1580 1581 1582 1583 1584 1585 1586 1587
	return 0;

notfound:
	if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
	if (content) *content = NULL;
	if (content_len) *content_len = 0;
	if (old_lineno) *old_lineno = -1;
	if (new_lineno) *new_lineno = -1;

	return GIT_ENOTFOUND;
1588
}
1589

1590 1591 1592 1593 1594
static int print_to_buffer_cb(
    const git_diff_delta *delta,
    const git_diff_range *range,
    char line_origin,
    const char *content,
1595 1596
    size_t content_len,
    void *payload)
1597
{
1598
	git_buf *output = payload;
1599
	GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
1600
	return git_buf_put(output, content, content_len);
1601 1602
}

1603 1604
int git_diff_patch_print(
	git_diff_patch *patch,
1605 1606
	git_diff_data_cb print_cb,
	void *payload)
1607 1608
{
	int error;
1609
	git_buf temp = GIT_BUF_INIT;
1610 1611 1612
	diff_print_info pi;
	size_t h, l;

1613
	assert(patch && print_cb);
1614

1615
	pi.diff     = patch->diff;
1616
	pi.print_cb = print_cb;
1617
	pi.payload  = payload;
1618 1619
	pi.buf      = &temp;

1620
	error = print_patch_file(patch->delta, 0, &pi);
1621

1622
	for (h = 0; h < patch->hunks_size && !error; ++h) {
1623 1624
		diff_patch_hunk *hunk = &patch->hunks[h];

1625 1626
		error = print_patch_hunk(
			patch->delta, &hunk->range, hunk->header, hunk->header_len, &pi);
1627

1628
		for (l = 0; l < hunk->line_count && !error; ++l) {
1629 1630 1631
			diff_patch_line *line = &patch->lines[hunk->line_start + l];

			error = print_patch_line(
1632 1633
				patch->delta, &hunk->range,
				line->origin, line->ptr, line->len, &pi);
1634 1635 1636
		}
	}

1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
	git_buf_free(&temp);

	return error;
}

int git_diff_patch_to_str(
	char **string,
	git_diff_patch *patch)
{
	int error;
	git_buf output = GIT_BUF_INIT;

1649
	error = git_diff_patch_print(patch, print_to_buffer_cb, &output);
1650

1651 1652 1653 1654 1655 1656
	/* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
	 * meaning a memory allocation failure, so just map to -1...
	 */
	if (error == GIT_EUSER)
		error = -1;

1657 1658 1659 1660
	*string = git_buf_detach(&output);

	return error;
}
1661 1662 1663 1664

int git_diff__paired_foreach(
	git_diff_list *idx2head,
	git_diff_list *wd2idx,
1665 1666
	int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
	void *payload)
1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698
{
	int cmp;
	git_diff_delta *i2h, *w2i;
	size_t i, j, i_max, j_max;
	bool icase = false;

	i_max = idx2head ? idx2head->deltas.length : 0;
	j_max = wd2idx   ? wd2idx->deltas.length   : 0;

	if (idx2head && wd2idx &&
		(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
		 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
	{
		/* Then use the ignore-case sorter... */
		icase = true;

		/* and assert that both are ignore-case sorted. If this function
		 * ever needs to support merge joining result sets that are not sorted
		 * by the same function, then it will need to be extended to do a spool
		 * and sort on one of the results before merge joining */
		assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
			0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
	}

	for (i = 0, j = 0; i < i_max || j < j_max; ) {
		i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
		w2i = wd2idx   ? GIT_VECTOR_GET(&wd2idx->deltas,j)   : NULL;

		cmp = !w2i ? -1 : !i2h ? 1 :
			STRCMP_CASESELECT(icase, i2h->old_file.path, w2i->old_file.path);

		if (cmp < 0) {
1699
			if (cb(i2h, NULL, payload))
1700 1701 1702
				return GIT_EUSER;
			i++;
		} else if (cmp > 0) {
1703
			if (cb(NULL, w2i, payload))
1704 1705 1706
				return GIT_EUSER;
			j++;
		} else {
1707
			if (cb(i2h, w2i, payload))
1708 1709 1710 1711 1712 1713 1714 1715
				return GIT_EUSER;
			i++; j++;
		}
	}

	return 0;
}