diff.c 43 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7
 *
 * 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 "diff.h"
9
#include "fileops.h"
10
#include "config.h"
11
#include "attr_file.h"
12
#include "filter.h"
13
#include "pathspec.h"
14 15
#include "index.h"
#include "odb.h"
16
#include "submodule.h"
17

18 19
#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
20 21
#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
	(VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
22

23
static git_diff_delta *diff_delta__alloc(
24
	git_diff *diff,
25
	git_delta_t status,
26
	const char *path)
27
{
28 29
	git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
	if (!delta)
30
		return NULL;
31

32
	delta->old_file.path = git_pool_strdup(&diff->pool, path);
33
	if (delta->old_file.path == NULL) {
34
		git__free(delta);
35
		return NULL;
36
	}
37

38
	delta->new_file.path = delta->old_file.path;
39

40
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
41
		switch (status) {
42 43
		case GIT_DELTA_ADDED:   status = GIT_DELTA_DELETED; break;
		case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
44 45 46
		default: break; /* leave other status values alone */
		}
	}
47 48
	delta->status = status;

49 50 51
	return delta;
}

52 53
static int diff_insert_delta(
	git_diff *diff, git_diff_delta *delta, const char *matched_pathspec)
54
{
55 56 57 58 59 60 61 62
	int error = 0;

	if (diff->opts.notify_cb) {
		error = diff->opts.notify_cb(
			diff, delta, matched_pathspec, diff->opts.notify_payload);

		if (error) {
			git__free(delta);
63 64 65 66 67

			if (error > 0)	/* positive value means to skip this delta */
				return 0;
			else			/* negative value means to cancel diff */
				return giterr_set_after_callback_function(error, "git_diff");
68 69 70 71 72
		}
	}

	if ((error = git_vector_insert(&diff->deltas, delta)) < 0)
		git__free(delta);
73

74
	return error;
75 76
}

77
static int diff_delta__from_one(
78
	git_diff *diff,
79
	git_delta_t status,
80
	const git_index_entry *entry)
81
{
Russell Belfer committed
82
	git_diff_delta *delta;
83
	const char *matched_pathspec;
Russell Belfer committed
84

85 86 87
	if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
		return 0;

Russell Belfer committed
88
	if (status == GIT_DELTA_IGNORED &&
89
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
Russell Belfer committed
90 91 92
		return 0;

	if (status == GIT_DELTA_UNTRACKED &&
93
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
Russell Belfer committed
94 95
		return 0;

96
	if (!git_pathspec__match(
97
			&diff->pathspec, entry->path,
98
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
99
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
100
			&matched_pathspec, NULL))
101 102
		return 0;

Russell Belfer committed
103
	delta = diff_delta__alloc(diff, status, entry->path);
104
	GITERR_CHECK_ALLOC(delta);
105

106
	/* This fn is just for single-sided diffs */
107
	assert(status != GIT_DELTA_MODIFIED);
108
	delta->nfiles = 1;
109

110
	if (delta->status == GIT_DELTA_DELETED) {
111 112
		delta->old_file.mode = entry->mode;
		delta->old_file.size = entry->file_size;
113
		git_oid_cpy(&delta->old_file.id, &entry->id);
114
	} else /* ADDED, IGNORED, UNTRACKED */ {
115 116
		delta->new_file.mode = entry->mode;
		delta->new_file.size = entry->file_size;
117
		git_oid_cpy(&delta->new_file.id, &entry->id);
118
	}
119

120
	delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
121 122

	if (delta->status == GIT_DELTA_DELETED ||
123 124
		!git_oid_iszero(&delta->new_file.id))
		delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
125

126
	return diff_insert_delta(diff, delta, matched_pathspec);
127 128
}

129
static int diff_delta__from_two(
130
	git_diff *diff,
131
	git_delta_t status,
132
	const git_index_entry *old_entry,
133
	uint32_t old_mode,
134
	const git_index_entry *new_entry,
135
	uint32_t new_mode,
136 137
	git_oid *new_oid,
	const char *matched_pathspec)
138
{
139
	git_diff_delta *delta;
140
	const char *canonical_path = old_entry->path;
141

Russell Belfer committed
142
	if (status == GIT_DELTA_UNMODIFIED &&
143
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
Russell Belfer committed
144 145
		return 0;

146
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
147 148
		uint32_t temp_mode = old_mode;
		const git_index_entry *temp_entry = old_entry;
149
		old_entry = new_entry;
150 151 152
		new_entry = temp_entry;
		old_mode = new_mode;
		new_mode = temp_mode;
153
	}
154

155
	delta = diff_delta__alloc(diff, status, canonical_path);
156
	GITERR_CHECK_ALLOC(delta);
157
	delta->nfiles = 2;
158

159
	git_oid_cpy(&delta->old_file.id, &old_entry->id);
160 161
	delta->old_file.size = old_entry->file_size;
	delta->old_file.mode = old_mode;
162
	delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
163

164
	git_oid_cpy(&delta->new_file.id, &new_entry->id);
165 166
	delta->new_file.size = new_entry->file_size;
	delta->new_file.mode = new_mode;
167 168

	if (new_oid) {
169
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
170
			git_oid_cpy(&delta->old_file.id, new_oid);
171
		else
172
			git_oid_cpy(&delta->new_file.id, new_oid);
173 174
	}

175
	if (new_oid || !git_oid_iszero(&new_entry->id))
176
		delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
177

178
	return diff_insert_delta(diff, delta, matched_pathspec);
179 180
}

181
static git_diff_delta *diff_delta__last_for_item(
182
	git_diff *diff,
183 184 185 186 187 188 189 190 191
	const git_index_entry *item)
{
	git_diff_delta *delta = git_vector_last(&diff->deltas);
	if (!delta)
		return NULL;

	switch (delta->status) {
	case GIT_DELTA_UNMODIFIED:
	case GIT_DELTA_DELETED:
192
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
193 194 195
			return delta;
		break;
	case GIT_DELTA_ADDED:
196
		if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
197 198
			return delta;
		break;
199 200
	case GIT_DELTA_UNTRACKED:
		if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
201
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
202 203
			return delta;
		break;
204
	case GIT_DELTA_MODIFIED:
205 206
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
207 208 209 210 211 212 213 214 215
			return delta;
		break;
	default:
		break;
	}

	return NULL;
}

216
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
217 218
{
	size_t len = strlen(prefix);
219 220 221 222 223 224

	/* append '/' at end if needed */
	if (len > 0 && prefix[len - 1] != '/')
		return git_pool_strcat(pool, prefix, "/");
	else
		return git_pool_strndup(pool, prefix, len + 1);
225 226
}

227 228 229 230 231 232 233 234 235 236 237 238 239
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
	const char *str = delta->old_file.path;

	if (!str ||
		delta->status == GIT_DELTA_ADDED ||
		delta->status == GIT_DELTA_RENAMED ||
		delta->status == GIT_DELTA_COPIED)
		str = delta->new_file.path;

	return str;
}

240 241 242 243 244
const char *git_diff_delta__path(const git_diff_delta *delta)
{
	return diff_delta__path(delta);
}

245
int git_diff_delta__cmp(const void *a, const void *b)
246 247
{
	const git_diff_delta *da = a, *db = b;
248
	int val = strcmp(diff_delta__path(da), diff_delta__path(db));
249 250 251
	return val ? val : ((int)da->status - (int)db->status);
}

252 253 254 255 256 257 258
int git_diff_delta__casecmp(const void *a, const void *b)
{
	const git_diff_delta *da = a, *db = b;
	int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
	return val ? val : ((int)da->status - (int)db->status);
}

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
{
	return delta->old_file.path ?
		delta->old_file.path : delta->new_file.path;
}

int git_diff_delta__i2w_cmp(const void *a, const void *b)
{
	const git_diff_delta *da = a, *db = b;
	int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
	return val ? val : ((int)da->status - (int)db->status);
}

int git_diff_delta__i2w_casecmp(const void *a, const void *b)
{
	const git_diff_delta *da = a, *db = b;
	int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
	return val ? val : ((int)da->status - (int)db->status);
}

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
bool git_diff_delta__should_skip(
	const git_diff_options *opts, const git_diff_delta *delta)
{
	uint32_t flags = opts ? opts->flags : 0;

	if (delta->status == GIT_DELTA_UNMODIFIED &&
		(flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
		return true;

	if (delta->status == GIT_DELTA_IGNORED &&
		(flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
		return true;

	if (delta->status == GIT_DELTA_UNTRACKED &&
		(flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
		return true;

	return false;
}


300 301
static const char *diff_mnemonic_prefix(
	git_iterator_type_t type, bool left_side)
302
{
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	const char *pfx = "";

	switch (type) {
	case GIT_ITERATOR_TYPE_EMPTY:   pfx = "c"; break;
	case GIT_ITERATOR_TYPE_TREE:    pfx = "c"; break;
	case GIT_ITERATOR_TYPE_INDEX:   pfx = "i"; break;
	case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
	case GIT_ITERATOR_TYPE_FS:      pfx = left_side ? "1" : "2"; break;
	default: break;
	}

	/* note: without a deeper look at pathspecs, there is no easy way
	 * to get the (o)bject / (w)ork tree mnemonics working...
	 */

	return pfx;
}

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
static void diff_set_ignore_case(git_diff *diff, bool ignore_case)
{
	if (!ignore_case) {
		diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;

		diff->strcomp    = git__strcmp;
		diff->strncomp   = git__strncmp;
		diff->pfxcomp    = git__prefixcmp;
		diff->entrycomp  = git_index_entry_cmp;

		git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
	} else {
		diff->opts.flags |= GIT_DIFF_IGNORE_CASE;

		diff->strcomp    = git__strcasecmp;
		diff->strncomp   = git__strncasecmp;
		diff->pfxcomp    = git__prefixcmp_icase;
		diff->entrycomp  = git_index_entry_icmp;

		git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
	}

	git_vector_sort(&diff->deltas);
}

346
static git_diff *diff_list_alloc(
347 348 349 350 351
	git_repository *repo,
	git_iterator *old_iter,
	git_iterator *new_iter)
{
	git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
352
	git_diff *diff = git__calloc(1, sizeof(git_diff));
353
	if (!diff)
354 355
		return NULL;

356 357
	assert(repo && old_iter && new_iter);

Russell Belfer committed
358
	GIT_REFCOUNT_INC(diff);
359
	diff->repo = repo;
360 361 362
	diff->old_src = old_iter->type;
	diff->new_src = new_iter->type;
	memcpy(&diff->opts, &dflt, sizeof(diff->opts));
363

364
	if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
365
		git_pool_init(&diff->pool, 1, 0) < 0) {
366
		git_diff_free(diff);
367 368 369 370 371
		return NULL;
	}

	/* Use case-insensitive compare if either iterator has
	 * the ignore_case bit set */
372 373 374 375
	diff_set_ignore_case(
		diff,
		git_iterator_ignore_case(old_iter) ||
		git_iterator_ignore_case(new_iter));
376 377 378 379 380

	return diff;
}

static int diff_list_apply_options(
381
	git_diff *diff,
382 383
	const git_diff_options *opts)
{
384
	git_config *cfg = NULL;
385 386 387 388 389 390
	git_repository *repo = diff->repo;
	git_pool *pool = &diff->pool;
	int val;

	if (opts) {
		/* copy user options (except case sensitivity info from iterators) */
Russell Belfer committed
391
		bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
392
		memcpy(&diff->opts, opts, sizeof(diff->opts));
Russell Belfer committed
393
		DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
394 395

		/* initialize pathspec from options */
396
		if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
397 398 399 400 401 402
			return -1;
	}

	/* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
		diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
403

404
	/* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
Russell Belfer committed
405
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
406 407
		diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;

408
	/* load config values that affect diff behavior */
409
	if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
410
		return val;
411

412
	if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
413
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
414

415
	if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
416
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT;
417 418

	if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
419
		!git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
420
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
421

422
	if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
423
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
424

425 426
	/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */

427 428 429
	/* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
	diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;

430 431
	/* If not given explicit `opts`, check `diff.xyz` configs */
	if (!opts) {
432
		int context = git_config__get_int_force(cfg, "diff.context", 3);
Linquize committed
433
		diff->opts.context_lines = context >= 0 ? (uint16_t)context : 3;
434

435 436 437
		/* add other defaults here */
	}

438 439 440 441 442 443 444
	/* Reverse src info if diff is reversed */
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
		git_iterator_type_t tmp_src = diff->old_src;
		diff->old_src = diff->new_src;
		diff->new_src = tmp_src;
	}

445 446 447 448 449 450 451 452
	/* Unset UPDATE_INDEX unless diffing workdir and index */
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
		(!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR ||
		   diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
		 !(diff->old_src == GIT_ITERATOR_TYPE_INDEX ||
		   diff->new_src == GIT_ITERATOR_TYPE_INDEX)))
		diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX;

453 454
	/* if ignore_submodules not explicitly set, check diff config */
	if (diff->opts.ignore_submodules <= 0) {
455 456
		const git_config_entry *entry;
		git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
457

458 459
		if (entry && git_submodule_parse_ignore(
				&diff->opts.ignore_submodules, entry->value) < 0)
460
			giterr_clear();
461
	}
462

463 464 465 466
	/* if either prefix is not set, figure out appropriate value */
	if (!diff->opts.old_prefix || !diff->opts.new_prefix) {
		const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
		const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
467

468
		if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
469
			use_old = use_new = "";
470
		else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
471 472 473
			use_old = diff_mnemonic_prefix(diff->old_src, true);
			use_new = diff_mnemonic_prefix(diff->new_src, false);
		}
474

475 476 477 478 479
		if (!diff->opts.old_prefix)
			diff->opts.old_prefix = use_old;
		if (!diff->opts.new_prefix)
			diff->opts.new_prefix = use_new;
	}
480

481 482 483
	/* strdup prefix from pool so we're not dependent on external data */
	diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix);
	diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix);
484

485
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
486 487 488
		const char *tmp_prefix = diff->opts.old_prefix;
		diff->opts.old_prefix  = diff->opts.new_prefix;
		diff->opts.new_prefix  = tmp_prefix;
489
	}
490

491 492 493 494
	git_config_free(cfg);

	/* check strdup results for error */
	return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0;
495 496
}

497
static void diff_list_free(git_diff *diff)
498
{
499
	git_vector_free_deep(&diff->deltas);
500

501
	git_pathspec__vfree(&diff->pathspec);
502
	git_pool_clear(&diff->pool);
503

504
	git__memzero(diff, sizeof(*diff));
505 506 507
	git__free(diff);
}

508
void git_diff_free(git_diff *diff)
Russell Belfer committed
509 510 511 512 513 514 515
{
	if (!diff)
		return;

	GIT_REFCOUNT_DEC(diff, diff_list_free);
}

516
void git_diff_addref(git_diff *diff)
517 518 519 520
{
	GIT_REFCOUNT_INC(diff);
}

521
int git_diff__oid_for_file(
522 523
	git_oid *out,
	git_diff *diff,
524 525
	const char *path,
	uint16_t  mode,
526
	git_off_t size)
527
{
528 529 530 531 532 533 534
	git_index_entry entry;

	memset(&entry, 0, sizeof(entry));
	entry.mode = mode;
	entry.file_size = size;
	entry.path = (char *)path;

535
	return git_diff__oid_for_entry(out, diff, &entry, NULL);
536 537 538
}

int git_diff__oid_for_entry(
539 540 541 542
	git_oid *out,
	git_diff *diff,
	const git_index_entry *src,
	const git_oid *update_match)
543
{
544
	int error = 0;
545
	git_buf full_path = GIT_BUF_INIT;
546
	git_index_entry entry = *src;
547 548 549
	git_filter_list *fl = NULL;

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

551
	if (git_buf_joinpath(
552
		&full_path, git_repository_workdir(diff->repo), entry.path) < 0)
553
		return -1;
554

555
	if (!entry.mode) {
556 557
		struct stat st;

558 559
		diff->perf.stat_calls++;

560
		if (p_stat(full_path.ptr, &st) < 0) {
561
			error = git_path_set_error(errno, entry.path, "stat");
562 563
			git_buf_free(&full_path);
			return error;
564 565
		}

566 567
		git_index_entry__init_from_stat(
			&entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
568 569
	}

570
	/* calculate OID for file if possible */
571
	if (S_ISGITLINK(entry.mode)) {
572 573
		git_submodule *sm;

574
		if (!git_submodule_lookup(&sm, diff->repo, entry.path)) {
575 576
			const git_oid *sm_oid = git_submodule_wd_id(sm);
			if (sm_oid)
577
				git_oid_cpy(out, sm_oid);
578 579
			git_submodule_free(sm);
		} else {
580 581 582 583 584
			/* if submodule lookup failed probably just in an intermediate
			 * state where some init hasn't happened, so ignore the error
			 */
			giterr_clear();
		}
585
	} else if (S_ISLNK(entry.mode)) {
586
		error = git_odb__hashlink(out, full_path.ptr);
587
		diff->perf.oid_calculations++;
588 589 590
	} else if (!git__is_sizet(entry.file_size)) {
		giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
			entry.path);
591 592
		error = -1;
	} else if (!(error = git_filter_list_load(
593 594
		&fl, diff->repo, NULL, entry.path,
		GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE)))
595 596 597 598 599 600
	{
		int fd = git_futils_open_ro(full_path.ptr);
		if (fd < 0)
			error = fd;
		else {
			error = git_odb__hashfd_filtered(
601
				out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
602
			p_close(fd);
603
			diff->perf.oid_calculations++;
604
		}
605 606

		git_filter_list_free(fl);
607 608
	}

609 610 611 612 613 614 615 616 617 618
	/* update index for entry if requested */
	if (!error && update_match && git_oid_equal(out, update_match)) {
		git_index *idx;

		if (!(error = git_repository_index(&idx, diff->repo))) {
			memcpy(&entry.id, out, sizeof(entry.id));
			error = git_index_add(idx, &entry);
			git_index_free(idx);
		}
 	}
619

620
	git_buf_free(&full_path);
621
	return error;
622 623
}

624 625 626
static bool diff_time_eq(
	const git_index_time *a, const git_index_time *b, bool use_nanos)
{
627
	return a->seconds == b->seconds &&
628 629 630
		(!use_nanos || a->nanoseconds == b->nanoseconds);
}

631 632 633 634 635 636 637 638
typedef struct {
	git_repository *repo;
	git_iterator *old_iter;
	git_iterator *new_iter;
	const git_index_entry *oitem;
	const git_index_entry *nitem;
} diff_in_progress;

639
#define MODE_BITS_MASK 0000777
640

641 642 643
static int maybe_modified_submodule(
	git_delta_t *status,
	git_oid *found_oid,
644
	git_diff *diff,
645 646 647 648 649
	diff_in_progress *info)
{
	int error = 0;
	git_submodule *sub;
	unsigned int sm_status = 0;
650
	git_submodule_ignore_t ign = diff->opts.ignore_submodules;
651 652 653

	*status = GIT_DELTA_UNMODIFIED;

654 655 656
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
		ign == GIT_SUBMODULE_IGNORE_ALL)
		return 0;
657

658 659 660 661 662 663 664 665 666
	if ((error = git_submodule_lookup(
			&sub, diff->repo, info->nitem->path)) < 0) {

		/* GIT_EEXISTS means dir with .git in it was found - ignore it */
		if (error == GIT_EEXISTS) {
			giterr_clear();
			error = 0;
		}
		return error;
667 668
	}

669
	if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
670 671
		/* ignore it */;
	else if ((error = git_submodule__status(
672
			&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
673
		/* return error below */;
674 675 676 677

	/* check IS_WD_UNMODIFIED because this case is only used
	 * when the new side of the diff is the working directory
	 */
678
	else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
679 680 681
		*status = GIT_DELTA_MODIFIED;

	/* now that we have a HEAD OID, check if HEAD moved */
682
	else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
683
		!git_oid_equal(&info->oitem->id, found_oid))
684 685
		*status = GIT_DELTA_MODIFIED;

686 687
	git_submodule_free(sub);
	return error;
688 689
}

690
static int maybe_modified(
691
	git_diff *diff,
692
	diff_in_progress *info)
693
{
694
	git_oid noid;
Russell Belfer committed
695
	git_delta_t status = GIT_DELTA_MODIFIED;
696 697
	const git_index_entry *oitem = info->oitem;
	const git_index_entry *nitem = info->nitem;
698 699
	unsigned int omode = oitem->mode;
	unsigned int nmode = nitem->mode;
700
	bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
701
	bool modified_uncertain = false;
702
	const char *matched_pathspec;
703
	int error = 0;
704

705
	if (!git_pathspec__match(
706
			&diff->pathspec, oitem->path,
707
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
708
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
709
			&matched_pathspec, NULL))
710 711
		return 0;

712 713
	memset(&noid, 0, sizeof(noid));

714
	/* on platforms with no symlinks, preserve mode of existing symlinks */
715 716
	if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
		!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
717
		nmode = omode;
718

719 720 721
	/* on platforms with no execmode, just preserve old mode */
	if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
		(nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
722
		new_is_workdir)
723
		nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
724

725
	/* support "assume unchanged" (poorly, b/c we still stat everything) */
726 727
	if ((oitem->flags & GIT_IDXENTRY_VALID) != 0)
		status = GIT_DELTA_UNMODIFIED;
728 729 730

	/* support "skip worktree" index bit */
	else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
Russell Belfer committed
731
		status = GIT_DELTA_UNMODIFIED;
732

Russell Belfer committed
733
	/* if basic type of file changed, then split into delete and add */
734
	else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
735
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
736 737
			status = GIT_DELTA_TYPECHANGE;
		else {
738 739 740
			if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
				error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem);
			return error;
741
		}
742 743
	}

744
	/* if oids and modes match (and are valid), then file is unmodified */
745
	else if (git_oid_equal(&oitem->id, &nitem->id) &&
746
			 omode == nmode &&
747
			 !git_oid_iszero(&oitem->id))
Russell Belfer committed
748
		status = GIT_DELTA_UNMODIFIED;
749

750 751
	/* if we have an unknown OID and a workdir iterator, then check some
	 * circumstances that can accelerate things or need special handling
752
	 */
753
	else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
754 755
		bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
		bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
756

757
		status = GIT_DELTA_UNMODIFIED;
Russell Belfer committed
758

759 760 761
		/* TODO: add check against index file st_mtime to avoid racy-git */

		if (S_ISGITLINK(nmode)) {
762 763
			if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
				return error;
764
		}
765 766 767 768

		/* if the stat data looks different, then mark modified - this just
		 * means that the OID will be recalculated below to confirm change
		 */
769 770 771 772 773 774
		else if (omode != nmode || oitem->file_size != nitem->file_size) {
			status = GIT_DELTA_MODIFIED;
			modified_uncertain =
				(oitem->file_size <= 0 && nitem->file_size > 0);
		}
		else if (!diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
775 776 777 778 779
			(use_ctime &&
			 !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
			oitem->ino != nitem->ino ||
			oitem->uid != nitem->uid ||
			oitem->gid != nitem->gid)
780
		{
781
			status = GIT_DELTA_MODIFIED;
782 783
			modified_uncertain = true;
		}
784
	}
785

786 787 788 789 790
	/* if mode is GITLINK and submodules are ignored, then skip */
	else if (S_ISGITLINK(nmode) &&
			 DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
		status = GIT_DELTA_UNMODIFIED;

791 792 793
	/* if we got here and decided that the files are modified, but we
	 * haven't calculated the OID of the new item, then calculate it now
	 */
794
	if (modified_uncertain && git_oid_iszero(&nitem->id)) {
795
		if (git_oid_iszero(&noid)) {
796 797 798 799 800 801
			const git_oid *update_check =
				DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) ?
				&oitem->id : NULL;

			if ((error = git_diff__oid_for_entry(
					&noid, diff, nitem, update_check)) < 0)
802
				return error;
803
		}
804 805 806 807 808 809

		/* if oid matches, then mark unmodified (except submodules, where
		 * the filesystem content may be modified even if the oid still
		 * matches between the index and the workdir HEAD)
		 */
		if (omode == nmode && !S_ISGITLINK(omode) &&
810
			git_oid_equal(&oitem->id, &noid))
Russell Belfer committed
811
			status = GIT_DELTA_UNMODIFIED;
812 813
	}

814
	return diff_delta__from_two(
815 816
		diff, status, oitem, omode, nitem, nmode,
		git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
817 818
}

819
static bool entry_is_prefixed(
820
	git_diff *diff,
821 822 823 824 825
	const git_index_entry *item,
	const git_index_entry *prefix_item)
{
	size_t pathlen;

826
	if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
827 828
		return false;

829
	pathlen = strlen(prefix_item->path);
830

831 832 833
	return (prefix_item->path[pathlen - 1] == '/' ||
			item->path[pathlen] == '\0' ||
			item->path[pathlen] == '/');
834 835
}

836
static int handle_unmatched_new_item(
837
	git_diff *diff, diff_in_progress *info)
838 839 840 841
{
	int error = 0;
	const git_index_entry *nitem = info->nitem;
	git_delta_t delta_type = GIT_DELTA_UNTRACKED;
842
	bool contains_oitem;
843

844 845 846
	/* check if this is a prefix of the other side */
	contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);

847 848 849
	/* update delta_type if this item is ignored */
	if (git_iterator_current_is_ignored(info->new_iter))
		delta_type = GIT_DELTA_IGNORED;
850

851
	if (nitem->mode == GIT_FILEMODE_TREE) {
852 853 854 855 856 857 858 859 860 861
		bool recurse_into_dir = contains_oitem;

		/* check if user requests recursion into this type of dir */
		recurse_into_dir = contains_oitem ||
			(delta_type == GIT_DELTA_UNTRACKED &&
			 DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
			(delta_type == GIT_DELTA_IGNORED &&
			 DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));

		/* do not advance into directories that contain a .git file */
862
		if (recurse_into_dir && !contains_oitem) {
863 864 865
			git_buf *full = NULL;
			if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
				return -1;
866 867
			if (full && git_path_contains(full, DOT_GIT)) {
				/* TODO: warning if not a valid git repository */
868
				recurse_into_dir = false;
869
			}
870
		}
871

872 873 874 875 876
		/* still have to look into untracked directories to match core git -
		 * with no untracked files, directory is treated as ignored
		 */
		if (!recurse_into_dir &&
			delta_type == GIT_DELTA_UNTRACKED &&
Russell Belfer committed
877
			DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
878 879
		{
			git_diff_delta *last;
880
			git_iterator_status_t untracked_state;
881 882

			/* attempt to insert record for this directory */
883
			if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
884 885 886 887 888 889 890 891
				return error;

			/* if delta wasn't created (because of rules), just skip ahead */
			last = diff_delta__last_for_item(diff, nitem);
			if (!last)
				return git_iterator_advance(&info->nitem, info->new_iter);

			/* iterate into dir looking for an actual untracked file */
892 893
			if ((error = git_iterator_advance_over_with_status(
					&info->nitem, &untracked_state, info->new_iter)) < 0 &&
894 895
				error != GIT_ITEROVER)
				return error;
896

897 898 899
			/* if we found nothing or just ignored items, update the record */
			if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
				untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
				last->status = GIT_DELTA_IGNORED;

				/* remove the record if we don't want ignored records */
				if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
					git_vector_pop(&diff->deltas);
					git__free(last);
				}
			}

			return 0;
		}

		/* try to advance into directory if necessary */
		if (recurse_into_dir) {
			error = git_iterator_advance_into(&info->nitem, info->new_iter);

			/* if real error or no error, proceed with iteration */
			if (error != GIT_ENOTFOUND)
				return error;
			giterr_clear();

			/* if directory is empty, can't advance into it, so either skip
			 * it or ignore it
			 */
			if (contains_oitem)
				return git_iterator_advance(&info->nitem, info->new_iter);
			delta_type = GIT_DELTA_IGNORED;
		}
928 929 930
	}

	else if (delta_type == GIT_DELTA_IGNORED &&
931 932
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
		git_iterator_current_tree_is_ignored(info->new_iter))
933
		/* item contained in ignored directory, so skip over it */
934 935 936 937 938
		return git_iterator_advance(&info->nitem, info->new_iter);

	else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
		delta_type = GIT_DELTA_ADDED;

939 940
	else if (nitem->mode == GIT_FILEMODE_COMMIT) {
		/* ignore things that are not actual submodules */
941
		if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
942 943
			giterr_clear();
			delta_type = GIT_DELTA_IGNORED;
944 945 946 947 948 949 950 951 952 953

			/* if this contains a tracked item, treat as normal TREE */
			if (contains_oitem) {
				error = git_iterator_advance_into(&info->nitem, info->new_iter);
				if (error != GIT_ENOTFOUND)
					return error;

				giterr_clear();
				return git_iterator_advance(&info->nitem, info->new_iter);
			}
954 955 956
		}
	}

957
	/* Actually create the record for this item if necessary */
958
	if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
959 960
		return error;

961 962
	/* If user requested TYPECHANGE records, then check for that instead of
	 * just generating an ADDED/UNTRACKED record
963 964 965
	 */
	if (delta_type != GIT_DELTA_IGNORED &&
		DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
966
		contains_oitem)
967 968 969 970 971 972 973 974 975 976 977 978 979
	{
		/* this entry was prefixed with a tree - make TYPECHANGE */
		git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
		if (last) {
			last->status = GIT_DELTA_TYPECHANGE;
			last->old_file.mode = GIT_FILEMODE_TREE;
		}
	}

	return git_iterator_advance(&info->nitem, info->new_iter);
}

static int handle_unmatched_old_item(
980
	git_diff *diff, diff_in_progress *info)
981 982
{
	int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem);
983
	if (error != 0)
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
		return error;

	/* if we are generating TYPECHANGE records then check for that
	 * instead of just generating a DELETE record
	 */
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
		entry_is_prefixed(diff, info->nitem, info->oitem))
	{
		/* this entry has become a tree! convert to TYPECHANGE */
		git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
		if (last) {
			last->status = GIT_DELTA_TYPECHANGE;
			last->new_file.mode = GIT_FILEMODE_TREE;
		}

		/* If new_iter is a workdir iterator, then this situation
		 * will certainly be followed by a series of untracked items.
		 * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
		 */
		if (S_ISDIR(info->nitem->mode) &&
			DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
			return git_iterator_advance(&info->nitem, info->new_iter);
	}

	return git_iterator_advance(&info->oitem, info->old_iter);
}

static int handle_matched_item(
1012
	git_diff *diff, diff_in_progress *info)
1013 1014 1015
{
	int error = 0;

1016 1017 1018 1019 1020
	if ((error = maybe_modified(diff, info)) < 0)
		return error;

	if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) ||
		error == GIT_ITEROVER)
1021 1022 1023 1024 1025
		error = git_iterator_advance(&info->nitem, info->new_iter);

	return error;
}

Russell Belfer committed
1026
int git_diff__from_iterators(
1027
	git_diff **diff_ptr,
1028
	git_repository *repo,
1029 1030
	git_iterator *old_iter,
	git_iterator *new_iter,
1031
	const git_diff_options *opts)
1032
{
1033
	int error = 0;
1034
	diff_in_progress info;
1035
	git_diff *diff;
1036 1037 1038 1039

	*diff_ptr = NULL;

	diff = diff_list_alloc(repo, old_iter, new_iter);
1040
	GITERR_CHECK_ALLOC(diff);
1041

1042 1043 1044 1045
	info.repo = repo;
	info.old_iter = old_iter;
	info.new_iter = new_iter;

1046
	/* make iterators have matching icase behavior */
Russell Belfer committed
1047
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
1048 1049 1050
		if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
			(error = git_iterator_set_ignore_case(new_iter, true)) < 0)
			goto cleanup;
1051 1052
	}

1053
	/* finish initialization */
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
	if ((error = diff_list_apply_options(diff, opts)) < 0)
		goto cleanup;

	if ((error = git_iterator_current(&info.oitem, old_iter)) < 0 &&
		error != GIT_ITEROVER)
		goto cleanup;
	if ((error = git_iterator_current(&info.nitem, new_iter)) < 0 &&
		error != GIT_ITEROVER)
		goto cleanup;
	error = 0;
1064

1065
	/* run iterators building diffs */
1066 1067 1068
	while (!error && (info.oitem || info.nitem)) {
		int cmp = info.oitem ?
			(info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
1069

1070
		/* create DELETED records for old items not matched in new */
1071 1072
		if (cmp < 0)
			error = handle_unmatched_old_item(diff, &info);
1073

1074 1075 1076
		/* create ADDED, TRACKED, or IGNORED records for new items not
		 * matched in old (and/or descend into directories as needed)
		 */
1077 1078
		else if (cmp > 0)
			error = handle_unmatched_new_item(diff, &info);
1079

1080 1081
		/* otherwise item paths match, so create MODIFIED record
		 * (or ADDED and DELETED pair if type changed)
1082
		 */
1083 1084
		else
			error = handle_matched_item(diff, &info);
1085 1086 1087 1088

		/* because we are iterating over two lists, ignore ITEROVER */
		if (error == GIT_ITEROVER)
			error = 0;
1089 1090
	}

1091 1092
	diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;

1093
cleanup:
1094 1095 1096
	if (!error)
		*diff_ptr = diff;
	else
1097
		git_diff_free(diff);
1098 1099

	return error;
1100 1101
}

1102
#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
1103 1104
	git_iterator *a = NULL, *b = NULL; \
	char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
Ben Straub committed
1105
	GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
1106
	if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
Russell Belfer committed
1107
		error = git_diff__from_iterators(diff, repo, a, b, opts); \
1108
	git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
1109
} while (0)
1110

1111
int git_diff_tree_to_tree(
1112
	git_diff **diff,
1113
	git_repository *repo,
1114 1115
	git_tree *old_tree,
	git_tree *new_tree,
1116
	const git_diff_options *opts)
1117
{
1118
	int error = 0;
1119
	git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
1120 1121 1122

	assert(diff && repo);

1123 1124 1125 1126
	/* for tree to tree diff, be case sensitive even if the index is
	 * currently case insensitive, unless the user explicitly asked
	 * for case insensitivity
	 */
Russell Belfer committed
1127
	if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
1128 1129
		iflag = GIT_ITERATOR_IGNORE_CASE;

1130
	DIFF_FROM_ITERATORS(
1131 1132
		git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
		git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
1133
	);
1134

1135
	return error;
1136 1137
}

1138 1139 1140 1141 1142
static int diff_load_index(git_index **index, git_repository *repo)
{
	int error = git_repository_index__weakptr(index, repo);

	/* reload the repository index when user did not pass one in */
1143
	if (!error && git_index_read(*index, false) < 0)
1144 1145 1146 1147 1148
		giterr_clear();

	return error;
}

1149
int git_diff_tree_to_index(
1150
	git_diff **diff,
1151
	git_repository *repo,
1152
	git_tree *old_tree,
1153
	git_index *index,
1154
	const git_diff_options *opts)
1155
{
1156
	int error = 0;
1157
	bool index_ignore_case = false;
1158 1159 1160

	assert(diff && repo);

1161
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1162 1163
		return error;

1164
	index_ignore_case = index->ignore_case;
1165

1166
	DIFF_FROM_ITERATORS(
1167 1168 1169 1170
		git_iterator_for_tree(
			&a, old_tree, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx),
		git_iterator_for_index(
			&b, index, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx)
1171
	);
1172

1173 1174 1175
	/* if index is in case-insensitive order, re-sort deltas to match */
	if (!error && index_ignore_case)
		diff_set_ignore_case(*diff, true);
1176

1177
	return error;
1178 1179
}

1180
int git_diff_index_to_workdir(
1181
	git_diff **diff,
1182
	git_repository *repo,
1183
	git_index *index,
1184
	const git_diff_options *opts)
1185
{
1186 1187 1188 1189
	int error = 0;

	assert(diff && repo);

1190
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1191 1192
		return error;

1193
	DIFF_FROM_ITERATORS(
1194
		git_iterator_for_index(&a, index, 0, pfx, pfx),
1195 1196
		git_iterator_for_workdir(
			&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1197
	);
1198

1199 1200 1201
	if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX))
		error = git_index_write(index);

1202
	return error;
1203 1204
}

1205
int git_diff_tree_to_workdir(
1206
	git_diff **diff,
1207
	git_repository *repo,
1208 1209
	git_tree *old_tree,
	const git_diff_options *opts)
1210
{
1211 1212 1213 1214
	int error = 0;

	assert(diff && repo);

1215
	DIFF_FROM_ITERATORS(
1216
		git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
1217 1218
		git_iterator_for_workdir(
			&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1219
	);
1220 1221

	return error;
1222
}
1223

Russell Belfer committed
1224 1225 1226 1227 1228 1229 1230 1231
int git_diff_tree_to_workdir_with_index(
	git_diff **diff,
	git_repository *repo,
	git_tree *old_tree,
	const git_diff_options *opts)
{
	int error = 0;
	git_diff *d1 = NULL, *d2 = NULL;
1232
	git_index *index = NULL;
Russell Belfer committed
1233 1234 1235

	assert(diff && repo);

1236 1237 1238 1239 1240
	if ((error = diff_load_index(&index, repo)) < 0)
		return error;

	if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) &&
		!(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
Russell Belfer committed
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
		error = git_diff_merge(d1, d2);

	git_diff_free(d2);

	if (error) {
		git_diff_free(d1);
		d1 = NULL;
	}

	*diff = d1;
	return error;
}

size_t git_diff_num_deltas(const git_diff *diff)
1255 1256
{
	assert(diff);
Russell Belfer committed
1257
	return diff->deltas.length;
1258 1259
}

Russell Belfer committed
1260
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
1261 1262
{
	size_t i, count = 0;
Russell Belfer committed
1263
	const git_diff_delta *delta;
1264 1265 1266 1267 1268 1269 1270 1271 1272 1273

	assert(diff);

	git_vector_foreach(&diff->deltas, i, delta) {
		count += (delta->status == type);
	}

	return count;
}

Russell Belfer committed
1274 1275 1276 1277 1278 1279
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
{
	assert(diff);
	return git_vector_get(&diff->deltas, idx);
}

1280
int git_diff_is_sorted_icase(const git_diff *diff)
1281
{
Russell Belfer committed
1282
	return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1283 1284
}

1285 1286
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
{
1287 1288
	assert(out);
	GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
1289 1290 1291 1292 1293
	out->stat_calls = diff->perf.stat_calls;
	out->oid_calculations = diff->perf.oid_calculations;
	return 0;
}

1294
int git_diff__paired_foreach(
1295 1296
	git_diff *head2idx,
	git_diff *idx2wd,
1297
	int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
1298 1299
	void *payload)
{
1300
	int cmp, error = 0;
1301
	git_diff_delta *h2i, *i2w;
1302
	size_t i, j, i_max, j_max;
1303
	int (*strcomp)(const char *, const char *) = git__strcmp;
1304
	bool h2i_icase, i2w_icase, icase_mismatch;
1305

1306 1307
	i_max = head2idx ? head2idx->deltas.length : 0;
	j_max = idx2wd ? idx2wd->deltas.length : 0;
1308 1309
	if (!i_max && !j_max)
		return 0;
1310

1311 1312 1313 1314 1315 1316 1317 1318
	/* At some point, tree-to-index diffs will probably never ignore case,
	 * even if that isn't true now.  Index-to-workdir diffs may or may not
	 * ignore case, but the index filename for the idx2wd diff should
	 * still be using the canonical case-preserving name.
	 *
	 * Therefore the main thing we need to do here is make sure the diffs
	 * are traversed in a compatible order.  To do this, we temporarily
	 * resort a mismatched diff to get the order correct.
1319 1320 1321 1322
	 *
	 * In order to traverse renames in the index->workdir, we need to
	 * ensure that we compare the index name on both sides, so we
	 * always sort by the old name in the i2w list.
1323
	 */
1324
	h2i_icase = head2idx != NULL &&
Russell Belfer committed
1325
		(head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1326 1327

	i2w_icase = idx2wd != NULL &&
Russell Belfer committed
1328
		(idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1329

1330
	icase_mismatch =
1331 1332 1333 1334 1335
		(head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);

	if (icase_mismatch && h2i_icase) {
		git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
		git_vector_sort(&head2idx->deltas);
1336
	}
1337 1338

	if (i2w_icase && !icase_mismatch) {
1339
		strcomp = git__strcasecmp;
1340

1341 1342 1343 1344 1345 1346 1347
		git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
		git_vector_sort(&idx2wd->deltas);
	} else if (idx2wd != NULL) {
		git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
		git_vector_sort(&idx2wd->deltas);
	}

1348
	for (i = 0, j = 0; i < i_max || j < j_max; ) {
1349 1350
		h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
		i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
1351

1352 1353
		cmp = !i2w ? -1 : !h2i ? 1 :
			strcomp(h2i->new_file.path, i2w->old_file.path);
1354 1355

		if (cmp < 0) {
1356
			i++; i2w = NULL;
1357
		} else if (cmp > 0) {
1358
			j++; h2i = NULL;
1359 1360 1361
		} else {
			i++; j++;
		}
1362

1363
		if ((error = cb(h2i, i2w, payload)) != 0) {
1364
			giterr_set_after_callback(error);
1365
			break;
1366
		}
1367 1368
	}

1369
	/* restore case-insensitive delta sort */
1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
	if (icase_mismatch && h2i_icase) {
		git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
		git_vector_sort(&head2idx->deltas);
	}

	/* restore idx2wd sort by new path */
	if (idx2wd != NULL) {
		git_vector_set_cmp(&idx2wd->deltas,
			i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
		git_vector_sort(&idx2wd->deltas);
1380 1381
	}

1382
	return error;
1383
}
1384

1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
int git_diff__commit(
	git_diff **diff,
	git_repository *repo,
	const git_commit *commit,
	const git_diff_options *opts)
{
	git_commit *parent = NULL;
	git_diff *commit_diff = NULL;
	git_tree *old_tree = NULL, *new_tree = NULL;
	size_t parents;
	int error = 0;

	if ((parents = git_commit_parentcount(commit)) > 1) {
		char commit_oidstr[GIT_OID_HEXSZ + 1];

		error = -1;
		giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
			git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
		goto on_error;
	}

	if (parents > 0)
		if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
			(error = git_commit_tree(&old_tree, parent)) < 0)
				goto on_error;

	if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
		(error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
			goto on_error;

	*diff = commit_diff;

on_error:
	git_tree_free(new_tree);
	git_tree_free(old_tree);
	git_commit_free(parent);

	return error;
}

int git_diff_format_email__append_header_tobuf(
	git_buf *out,
	const git_oid *id,
	const git_signature *author,
	const char *summary,
	size_t patch_no,
	size_t total_patches,
	bool exclude_patchno_marker)
{
	char idstr[GIT_OID_HEXSZ + 1];
	char date_str[GIT_DATE_RFC2822_SZ];
	int error = 0;

	git_oid_fmt(idstr, id);
	idstr[GIT_OID_HEXSZ] = '\0';

	if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), &author->when)) < 0)
		return error;

	error = git_buf_printf(out,
				"From %s Mon Sep 17 00:00:00 2001\n" \
				"From: %s <%s>\n" \
				"Date: %s\n" \
				"Subject: ",
				idstr,
				author->name, author->email,
				date_str);

	if (error < 0)
		return error;

	if (!exclude_patchno_marker) {
		if (total_patches == 1) {
			error = git_buf_puts(out, "[PATCH] ");
		} else {
			error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", patch_no, total_patches);
		}

		if (error < 0)
			return error;
	}

	error = git_buf_printf(out, "%s\n\n", summary);

	return error;
}

int git_diff_format_email__append_patches_tobuf(
	git_buf *out,
	git_diff *diff)
{
	size_t i, deltas;
	int error = 0;

	deltas = git_diff_num_deltas(diff);

	for (i = 0; i < deltas; ++i) {
		git_patch *patch = NULL;

		if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
			error = git_patch_to_buf(out, patch);

		git_patch_free(patch);

		if (error < 0)
			break;
	}

	return error;
}

int git_diff_format_email(
	git_buf *out,
	git_diff *diff,
	const git_diff_format_email_options *opts)
{
	git_diff_stats *stats = NULL;
1502
	char *summary = NULL, *loc = NULL;
1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523
	bool ignore_marker;
	unsigned int format_flags = 0;
	int error;

	assert(out && diff && opts);
	assert(opts->summary && opts->id && opts->author);

	GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options");

	if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) {
		if (opts->patch_no > opts->total_patches) {
			giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches);
			return -1;
		}

		if (opts->patch_no == 0) {
			giterr_set(GITERR_INVALID, "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
			return -1;
		}
	}

1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539
	/* the summary we receive may not be clean.
	 * it could potentially contain new line characters
	 * or not be set, sanitize, */
	if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
		size_t offset = 0;

		if ((offset = (loc - opts->summary)) == 0) {
			giterr_set(GITERR_INVALID, "summary is empty");
			error = -1;
		}

		summary = git__calloc(offset + 1, sizeof(char));
		GITERR_CHECK_ALLOC(summary);
		strncpy(summary, opts->summary, offset);
	}

1540
	error = git_diff_format_email__append_header_tobuf(out,
1541
				opts->id, opts->author, summary == NULL ? opts->summary : summary,
1542 1543 1544 1545 1546 1547 1548 1549 1550
				opts->patch_no, opts->total_patches, ignore_marker);

	if (error < 0)
		goto on_error;

	format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;

	if ((error = git_buf_puts(out, "---\n")) < 0 ||
		(error = git_diff_get_stats(&stats, diff)) < 0 ||
1551 1552
		(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
		(error = git_buf_putc(out, '\n')) < 0 ||
1553 1554 1555 1556 1557 1558
		(error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0)
			goto on_error;

	error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");

on_error:
1559
	git__free(summary);
1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595
	git_diff_stats_free(stats);

	return error;
}

int git_diff_commit_as_email(
	git_buf *out,
	git_repository *repo,
	git_commit *commit,
	size_t patch_no,
	size_t total_patches,
	git_diff_format_email_flags_t flags,
	const git_diff_options *diff_opts)
{
	git_diff *diff = NULL;
	git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
	int error;

	assert (out && repo && commit);

	opts.flags = flags;
	opts.patch_no = patch_no;
	opts.total_patches = total_patches;
	opts.id = git_commit_id(commit);
	opts.summary = git_commit_summary(commit);
	opts.author = git_commit_author(commit);

	if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
		return error;

	error = git_diff_format_email(out, diff, &opts);

	git_diff_free(diff);
	return error;
}

1596
int git_diff_init_options(git_diff_options *opts, unsigned int version)
1597
{
1598 1599
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
1600
	return 0;
1601 1602
}

1603 1604
int git_diff_find_init_options(
	git_diff_find_options *opts, unsigned int version)
1605
{
1606 1607
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
1608
	return 0;
1609
}
1610

1611 1612
int git_diff_format_email_init_options(
	git_diff_format_email_options *opts, unsigned int version)
1613
{
1614 1615 1616
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_format_email_options,
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
1617
	return 0;
1618
}