diff.c 47.4 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 81
	const git_index_entry *oitem,
	const git_index_entry *nitem)
82
{
83 84
	const git_index_entry *entry = nitem;
	bool has_old = false;
Russell Belfer committed
85
	git_diff_delta *delta;
86
	const char *matched_pathspec;
Russell Belfer committed
87

88 89 90 91 92 93 94 95 96 97
	assert((oitem != NULL) ^ (nitem != NULL));

	if (oitem) {
		entry = oitem;
		has_old = true;
	}

	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
		has_old = !has_old;

98 99 100
	if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
		return 0;

Russell Belfer committed
101
	if (status == GIT_DELTA_IGNORED &&
102
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
Russell Belfer committed
103 104 105
		return 0;

	if (status == GIT_DELTA_UNTRACKED &&
106
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
Russell Belfer committed
107
		return 0;
108 109 110 111
	
	if (status == GIT_DELTA_UNREADABLE &&
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
		return 0;
Russell Belfer committed
112

113
	if (!git_pathspec__match(
114
			&diff->pathspec, entry->path,
115
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
116
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
117
			&matched_pathspec, NULL))
118 119
		return 0;

Russell Belfer committed
120
	delta = diff_delta__alloc(diff, status, entry->path);
121
	GITERR_CHECK_ALLOC(delta);
122

123
	/* This fn is just for single-sided diffs */
124
	assert(status != GIT_DELTA_MODIFIED);
125
	delta->nfiles = 1;
126

127
	if (has_old) {
128 129
		delta->old_file.mode = entry->mode;
		delta->old_file.size = entry->file_size;
130
		delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
131
		git_oid_cpy(&delta->old_file.id, &entry->id);
132
	} else /* ADDED, IGNORED, UNTRACKED */ {
133 134
		delta->new_file.mode = entry->mode;
		delta->new_file.size = entry->file_size;
135
		delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
136
		git_oid_cpy(&delta->new_file.id, &entry->id);
137
	}
138

139
	delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
140

141
	if (has_old || !git_oid_iszero(&delta->new_file.id))
142
		delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
143

144
	return diff_insert_delta(diff, delta, matched_pathspec);
145 146
}

147
static int diff_delta__from_two(
148
	git_diff *diff,
149
	git_delta_t status,
150
	const git_index_entry *old_entry,
151
	uint32_t old_mode,
152
	const git_index_entry *new_entry,
153
	uint32_t new_mode,
154
	const git_oid *new_id,
155
	const char *matched_pathspec)
156
{
157
	const git_oid *old_id = &old_entry->id;
158
	git_diff_delta *delta;
159
	const char *canonical_path = old_entry->path;
160

Russell Belfer committed
161
	if (status == GIT_DELTA_UNMODIFIED &&
162
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
Russell Belfer committed
163 164
		return 0;

165 166 167
	if (!new_id)
		new_id = &new_entry->id;

168
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
169 170
		uint32_t temp_mode = old_mode;
		const git_index_entry *temp_entry = old_entry;
171 172
		const git_oid *temp_id = old_id;

173
		old_entry = new_entry;
174 175 176
		new_entry = temp_entry;
		old_mode = new_mode;
		new_mode = temp_mode;
177 178
		old_id = new_id;
		new_id = temp_id;
179
	}
180

181
	delta = diff_delta__alloc(diff, status, canonical_path);
182
	GITERR_CHECK_ALLOC(delta);
183
	delta->nfiles = 2;
184

185
	if (!git_index_entry_is_conflict(old_entry)) {
186 187 188
		delta->old_file.size = old_entry->file_size;
		delta->old_file.mode = old_mode;
		git_oid_cpy(&delta->old_file.id, old_id);
189 190
		delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
			GIT_DIFF_FLAG_EXISTS;
191
	}
192

193
	if (!git_index_entry_is_conflict(new_entry)) {
194 195 196
		git_oid_cpy(&delta->new_file.id, new_id);
		delta->new_file.size = new_entry->file_size;
		delta->new_file.mode = new_mode;
197 198
		delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
		delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
199

200 201
		if (!git_oid_iszero(&new_entry->id))
			delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
202 203
	}

204
	return diff_insert_delta(diff, delta, matched_pathspec);
205 206
}

207
static git_diff_delta *diff_delta__last_for_item(
208
	git_diff *diff,
209 210 211 212 213 214 215 216 217
	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:
218
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
219 220 221
			return delta;
		break;
	case GIT_DELTA_ADDED:
222
		if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
223 224
			return delta;
		break;
225
	case GIT_DELTA_UNREADABLE:
226
	case GIT_DELTA_UNTRACKED:
227 228 229 230
		if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
			return delta;
		break;
231
	case GIT_DELTA_MODIFIED:
232 233
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
234 235 236 237 238 239 240 241 242
			return delta;
		break;
	default:
		break;
	}

	return NULL;
}

243
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
244 245
{
	size_t len = strlen(prefix);
246 247 248 249 250 251

	/* 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);
252 253
}

254 255 256 257 258 259 260 261 262 263 264 265 266
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;
}

267 268 269 270 271
const char *git_diff_delta__path(const git_diff_delta *delta)
{
	return diff_delta__path(delta);
}

272
int git_diff_delta__cmp(const void *a, const void *b)
273 274
{
	const git_diff_delta *da = a, *db = b;
275
	int val = strcmp(diff_delta__path(da), diff_delta__path(db));
276 277 278
	return val ? val : ((int)da->status - (int)db->status);
}

279 280 281 282 283 284 285
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);
}

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
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);
}

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
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;

323 324 325 326
	if (delta->status == GIT_DELTA_UNREADABLE &&
		(flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
		return true;

327 328 329 330
	return false;
}


331 332
static const char *diff_mnemonic_prefix(
	git_iterator_type_t type, bool left_side)
333
{
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
	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;
}

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
static int diff_entry_cmp(const void *a, const void *b)
{
	const git_index_entry *entry_a = a;
	const git_index_entry *entry_b = b;

	return strcmp(entry_a->path, entry_b->path);
}

static int diff_entry_icmp(const void *a, const void *b)
{
	const git_index_entry *entry_a = a;
	const git_index_entry *entry_b = b;

	return strcasecmp(entry_a->path, entry_b->path);
}

368 369 370 371 372 373 374 375
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;
376
		diff->entrycomp  = diff_entry_cmp;
377 378 379 380 381 382 383 384

		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;
385
		diff->entrycomp  = diff_entry_icmp;
386 387 388 389 390 391 392

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

	git_vector_sort(&diff->deltas);
}

393
static git_diff *diff_list_alloc(
394 395 396 397 398
	git_repository *repo,
	git_iterator *old_iter,
	git_iterator *new_iter)
{
	git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
399
	git_diff *diff = git__calloc(1, sizeof(git_diff));
400
	if (!diff)
401 402
		return NULL;

403 404
	assert(repo && old_iter && new_iter);

Russell Belfer committed
405
	GIT_REFCOUNT_INC(diff);
406
	diff->repo = repo;
407 408 409
	diff->old_src = old_iter->type;
	diff->new_src = new_iter->type;
	memcpy(&diff->opts, &dflt, sizeof(diff->opts));
410

411
	if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
412
		git_pool_init(&diff->pool, 1, 0) < 0) {
413
		git_diff_free(diff);
414 415 416 417 418
		return NULL;
	}

	/* Use case-insensitive compare if either iterator has
	 * the ignore_case bit set */
419 420 421 422
	diff_set_ignore_case(
		diff,
		git_iterator_ignore_case(old_iter) ||
		git_iterator_ignore_case(new_iter));
423 424 425 426 427

	return diff;
}

static int diff_list_apply_options(
428
	git_diff *diff,
429 430
	const git_diff_options *opts)
{
431
	git_config *cfg = NULL;
432 433 434 435 436 437
	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
438
		bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
439
		memcpy(&diff->opts, opts, sizeof(diff->opts));
Russell Belfer committed
440
		DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
441 442

		/* initialize pathspec from options */
443
		if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
444 445 446 447 448 449
			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;
450

451
	/* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
Russell Belfer committed
452
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
453 454
		diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;

455
	/* load config values that affect diff behavior */
456
	if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
457
		return val;
458

459
	if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
460
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
461

462
	if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
463
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT;
464 465

	if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
466
		!git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
467
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
468

469
	if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
470
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
471

472 473
	/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */

474 475 476
	/* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
	diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;

477 478
	/* If not given explicit `opts`, check `diff.xyz` configs */
	if (!opts) {
479
		int context = git_config__get_int_force(cfg, "diff.context", 3);
480
		diff->opts.context_lines = context >= 0 ? (uint32_t)context : 3;
481

482 483 484
		/* add other defaults here */
	}

485 486 487 488 489 490 491
	/* 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;
	}

492 493 494 495 496 497 498 499
	/* 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;

500 501
	/* if ignore_submodules not explicitly set, check diff config */
	if (diff->opts.ignore_submodules <= 0) {
502
		 git_config_entry *entry;
503
		git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
504

505 506
		if (entry && git_submodule_parse_ignore(
				&diff->opts.ignore_submodules, entry->value) < 0)
507
			giterr_clear();
508
		git_config_entry_free(entry);
509
	}
510

511 512 513 514
	/* 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;
515

516
		if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
517
			use_old = use_new = "";
518
		else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
519 520 521
			use_old = diff_mnemonic_prefix(diff->old_src, true);
			use_new = diff_mnemonic_prefix(diff->new_src, false);
		}
522

523 524 525 526 527
		if (!diff->opts.old_prefix)
			diff->opts.old_prefix = use_old;
		if (!diff->opts.new_prefix)
			diff->opts.new_prefix = use_new;
	}
528

529 530 531
	/* 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);
532

533
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
534 535 536
		const char *tmp_prefix = diff->opts.old_prefix;
		diff->opts.old_prefix  = diff->opts.new_prefix;
		diff->opts.new_prefix  = tmp_prefix;
537
	}
538

539 540 541 542
	git_config_free(cfg);

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

545
static void diff_list_free(git_diff *diff)
546
{
547
	git_vector_free_deep(&diff->deltas);
548

549
	git_pathspec__vfree(&diff->pathspec);
550
	git_pool_clear(&diff->pool);
551

552
	git__memzero(diff, sizeof(*diff));
553 554 555
	git__free(diff);
}

556
void git_diff_free(git_diff *diff)
Russell Belfer committed
557 558 559 560 561 562 563
{
	if (!diff)
		return;

	GIT_REFCOUNT_DEC(diff, diff_list_free);
}

564
void git_diff_addref(git_diff *diff)
565 566 567 568
{
	GIT_REFCOUNT_INC(diff);
}

569
int git_diff__oid_for_file(
570 571
	git_oid *out,
	git_diff *diff,
572
	const char *path,
573
	uint16_t mode,
574
	git_off_t size)
575
{
576 577 578 579 580 581 582
	git_index_entry entry;

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

583
	return git_diff__oid_for_entry(out, diff, &entry, mode, NULL);
584 585 586
}

int git_diff__oid_for_entry(
587 588 589
	git_oid *out,
	git_diff *diff,
	const git_index_entry *src,
590
	uint16_t mode,
591
	const git_oid *update_match)
592
{
593
	int error = 0;
594
	git_buf full_path = GIT_BUF_INIT;
595
	git_index_entry entry = *src;
596 597 598
	git_filter_list *fl = NULL;

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

600
	if (git_buf_joinpath(
601
		&full_path, git_repository_workdir(diff->repo), entry.path) < 0)
602
		return -1;
603

604
	if (!mode) {
605 606
		struct stat st;

607 608
		diff->perf.stat_calls++;

609
		if (p_stat(full_path.ptr, &st) < 0) {
610
			error = git_path_set_error(errno, entry.path, "stat");
611 612
			git_buf_free(&full_path);
			return error;
613 614
		}

615 616
		git_index_entry__init_from_stat(
			&entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
617 618
	}

619
	/* calculate OID for file if possible */
620
	if (S_ISGITLINK(mode)) {
621 622
		git_submodule *sm;

623
		if (!git_submodule_lookup(&sm, diff->repo, entry.path)) {
624 625
			const git_oid *sm_oid = git_submodule_wd_id(sm);
			if (sm_oid)
626
				git_oid_cpy(out, sm_oid);
627 628
			git_submodule_free(sm);
		} else {
629 630 631 632 633
			/* if submodule lookup failed probably just in an intermediate
			 * state where some init hasn't happened, so ignore the error
			 */
			giterr_clear();
		}
634
	} else if (S_ISLNK(mode)) {
635
		error = git_odb__hashlink(out, full_path.ptr);
636
		diff->perf.oid_calculations++;
637 638 639
	} else if (!git__is_sizet(entry.file_size)) {
		giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
			entry.path);
640 641
		error = -1;
	} else if (!(error = git_filter_list_load(
642
		&fl, diff->repo, NULL, entry.path,
643
		GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)))
644 645 646 647 648 649
	{
		int fd = git_futils_open_ro(full_path.ptr);
		if (fd < 0)
			error = fd;
		else {
			error = git_odb__hashfd_filtered(
650
				out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
651
			p_close(fd);
652
			diff->perf.oid_calculations++;
653
		}
654 655

		git_filter_list_free(fl);
656 657
	}

658 659 660
	/* update index for entry if requested */
	if (!error && update_match && git_oid_equal(out, update_match)) {
		git_index *idx;
661
		git_index_entry updated_entry;
662

663 664 665 666 667 668
		memcpy(&updated_entry, &entry, sizeof(git_index_entry));
		updated_entry.mode = mode;
		git_oid_cpy(&updated_entry.id, out);

		if (!(error = git_repository_index__weakptr(&idx, diff->repo)))
			error = git_index_add(idx, &updated_entry);
669
 	}
670

671
	git_buf_free(&full_path);
672
	return error;
673 674
}

675 676 677
static bool diff_time_eq(
	const git_index_time *a, const git_index_time *b, bool use_nanos)
{
678
	return a->seconds == b->seconds &&
679 680 681
		(!use_nanos || a->nanoseconds == b->nanoseconds);
}

682 683 684 685 686 687 688 689
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;

690
#define MODE_BITS_MASK 0000777
691

692 693 694
static int maybe_modified_submodule(
	git_delta_t *status,
	git_oid *found_oid,
695
	git_diff *diff,
696 697 698 699 700
	diff_in_progress *info)
{
	int error = 0;
	git_submodule *sub;
	unsigned int sm_status = 0;
701
	git_submodule_ignore_t ign = diff->opts.ignore_submodules;
702 703 704

	*status = GIT_DELTA_UNMODIFIED;

705 706 707
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
		ign == GIT_SUBMODULE_IGNORE_ALL)
		return 0;
708

709 710 711 712 713 714 715 716 717
	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;
718 719
	}

720
	if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
721 722
		/* ignore it */;
	else if ((error = git_submodule__status(
723
			&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
724
		/* return error below */;
725 726 727 728

	/* check IS_WD_UNMODIFIED because this case is only used
	 * when the new side of the diff is the working directory
	 */
729
	else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
730 731 732
		*status = GIT_DELTA_MODIFIED;

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

737 738
	git_submodule_free(sub);
	return error;
739 740
}

741
static int maybe_modified(
742
	git_diff *diff,
743
	diff_in_progress *info)
744
{
745
	git_oid noid;
Russell Belfer committed
746
	git_delta_t status = GIT_DELTA_MODIFIED;
747 748
	const git_index_entry *oitem = info->oitem;
	const git_index_entry *nitem = info->nitem;
749 750
	unsigned int omode = oitem->mode;
	unsigned int nmode = nitem->mode;
751
	bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
752
	bool modified_uncertain = false;
753
	const char *matched_pathspec;
754
	int error = 0;
755

756
	if (!git_pathspec__match(
757
			&diff->pathspec, oitem->path,
758
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
759
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
760
			&matched_pathspec, NULL))
761 762
		return 0;

763 764
	memset(&noid, 0, sizeof(noid));

765
	/* on platforms with no symlinks, preserve mode of existing symlinks */
766 767
	if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
		!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
768
		nmode = omode;
769

770 771 772
	/* 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) &&
773
		new_is_workdir)
774
		nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
775

776
	/* if one side is a conflict, mark the whole delta as conflicted */
777
	if (git_index_entry_is_conflict(oitem) ||
778
			git_index_entry_is_conflict(nitem)) {
779 780
		status = GIT_DELTA_CONFLICTED;

781
	/* support "assume unchanged" (poorly, b/c we still stat everything) */
782
	} else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) {
783
		status = GIT_DELTA_UNMODIFIED;
784 785

	/* support "skip worktree" index bit */
786
	} else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) {
Russell Belfer committed
787
		status = GIT_DELTA_UNMODIFIED;
788

Russell Belfer committed
789
	/* if basic type of file changed, then split into delete and add */
790 791
	} else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) {
792
			status = GIT_DELTA_TYPECHANGE;
793 794
		}

795
		else if (nmode == GIT_FILEMODE_UNREADABLE) {
796 797
			if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
				error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem);
798 799
			return error;
		}
800

801
		else {
802 803
			if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
				error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
804
			return error;
805
		}
806

807
	/* if oids and modes match (and are valid), then file is unmodified */
808
	} else if (git_oid_equal(&oitem->id, &nitem->id) &&
809
			 omode == nmode &&
810
			 !git_oid_iszero(&oitem->id)) {
Russell Belfer committed
811
		status = GIT_DELTA_UNMODIFIED;
812

813 814
	/* if we have an unknown OID and a workdir iterator, then check some
	 * circumstances that can accelerate things or need special handling
815
	 */
816
	} else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
817 818
		bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
		bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
819 820
		git_index *index;
		git_iterator_index(&index, info->new_iter);
821

822
		status = GIT_DELTA_UNMODIFIED;
Russell Belfer committed
823

824
		if (S_ISGITLINK(nmode)) {
825 826
			if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
				return error;
827
		}
828 829 830 831

		/* if the stat data looks different, then mark modified - this just
		 * means that the OID will be recalculated below to confirm change
		 */
832 833 834 835 836 837
		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) ||
838 839 840 841
			(use_ctime &&
			 !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
			oitem->ino != nitem->ino ||
			oitem->uid != nitem->uid ||
842 843
			oitem->gid != nitem->gid ||
			(index && nitem->mtime.seconds >= index->stamp.mtime))
844
		{
845
			status = GIT_DELTA_MODIFIED;
846 847
			modified_uncertain = true;
		}
848

849
	/* if mode is GITLINK and submodules are ignored, then skip */
850 851
	} else if (S_ISGITLINK(nmode) &&
			 DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) {
852
		status = GIT_DELTA_UNMODIFIED;
853
	}
854

855 856 857
	/* 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
	 */
858
	if (modified_uncertain && git_oid_iszero(&nitem->id)) {
859
		const git_oid *update_check =
860
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ?
861
			&oitem->id : NULL;
862

863
		if ((error = git_diff__oid_for_entry(
864
				&noid, diff, nitem, nmode, update_check)) < 0)
865
			return error;
866 867 868 869 870 871

		/* 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) &&
872
			git_oid_equal(&oitem->id, &noid))
Russell Belfer committed
873
			status = GIT_DELTA_UNMODIFIED;
874 875
	}

876 877 878 879 880 881 882 883
	/* If we want case changes, then break this into a delete of the old
	 * and an add of the new so that consumers can act accordingly (eg,
	 * checkout will update the case on disk.)
	 */
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) &&
		DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) &&
		strcmp(oitem->path, nitem->path) != 0) {

884 885 886
		if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
			error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);

887 888 889
		return error;
	}

890
	return diff_delta__from_two(
891 892
		diff, status, oitem, omode, nitem, nmode,
		git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
893 894
}

895
static bool entry_is_prefixed(
896
	git_diff *diff,
897 898 899 900 901
	const git_index_entry *item,
	const git_index_entry *prefix_item)
{
	size_t pathlen;

902
	if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
903 904
		return false;

905
	pathlen = strlen(prefix_item->path);
906

907 908 909
	return (prefix_item->path[pathlen - 1] == '/' ||
			item->path[pathlen] == '\0' ||
			item->path[pathlen] == '/');
910 911
}

912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
static int iterator_current(
	const git_index_entry **entry,
	git_iterator *iterator)
{
	int error;

	if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) {
		*entry = NULL;
		error = 0;
	}

	return error;
}

static int iterator_advance(
	const git_index_entry **entry,
	git_iterator *iterator)
{
930 931
	const git_index_entry *prev_entry = *entry;
	int cmp, error;
932

933 934 935 936 937 938 939
	/* if we're looking for conflicts, we only want to report
	 * one conflict for each file, instead of all three sides.
	 * so if this entry is a conflict for this file, and the
	 * previous one was a conflict for the same file, skip it.
	 */
	while ((error = git_iterator_advance(entry, iterator)) == 0) {
		if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) ||
940 941
			!git_index_entry_is_conflict(prev_entry) ||
			!git_index_entry_is_conflict(*entry))
942 943 944 945 946 947 948 949 950 951 952
			break;

		cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ?
			strcasecmp(prev_entry->path, (*entry)->path) :
			strcmp(prev_entry->path, (*entry)->path);

		if (cmp)
			break;
	}

	if (error == GIT_ITEROVER) {
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
		*entry = NULL;
		error = 0;
	}

	return error;
}

static int iterator_advance_into(
	const git_index_entry **entry,
	git_iterator *iterator)
{
	int error;

	if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) {
		*entry = NULL;
		error = 0;
	}

	return error;
}

static int iterator_advance_over_with_status(
	const git_index_entry **entry,
	git_iterator_status_t *status,
	git_iterator *iterator)
{
	int error;

	if ((error = git_iterator_advance_over_with_status(
			entry, status, iterator)) == GIT_ITEROVER) {
		*entry = NULL;
		error = 0;
	}

	return error;
}

990
static int handle_unmatched_new_item(
991
	git_diff *diff, diff_in_progress *info)
992 993 994 995
{
	int error = 0;
	const git_index_entry *nitem = info->nitem;
	git_delta_t delta_type = GIT_DELTA_UNTRACKED;
996
	bool contains_oitem;
997

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

1001
	/* update delta_type if this item is conflicted */
1002
	if (git_index_entry_is_conflict(nitem))
1003 1004
		delta_type = GIT_DELTA_CONFLICTED;

1005
	/* update delta_type if this item is ignored */
1006
	else if (git_iterator_current_is_ignored(info->new_iter))
1007
		delta_type = GIT_DELTA_IGNORED;
1008

1009
	if (nitem->mode == GIT_FILEMODE_TREE) {
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
		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 */
1020
		if (recurse_into_dir && !contains_oitem) {
1021 1022 1023
			git_buf *full = NULL;
			if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
				return -1;
1024 1025
			if (full && git_path_contains(full, DOT_GIT)) {
				/* TODO: warning if not a valid git repository */
1026
				recurse_into_dir = false;
1027
			}
1028
		}
1029

1030 1031 1032 1033 1034
		/* 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
1035
			DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
1036 1037
		{
			git_diff_delta *last;
1038
			git_iterator_status_t untracked_state;
1039 1040

			/* attempt to insert record for this directory */
1041
			if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
1042 1043 1044 1045 1046
				return error;

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

			/* iterate into dir looking for an actual untracked file */
1050 1051
			if ((error = iterator_advance_over_with_status(
					&info->nitem, &untracked_state, info->new_iter)) < 0)
1052
				return error;
1053

1054 1055 1056
			/* if we found nothing or just ignored items, update the record */
			if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
				untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
				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) {
1071
			error = iterator_advance_into(&info->nitem, info->new_iter);
1072

1073 1074 1075 1076
			/* if real error or no error, proceed with iteration */
			if (error != GIT_ENOTFOUND)
				return error;
			giterr_clear();
1077

1078 1079 1080
			/* if directory is empty, can't advance into it, so either skip
			 * it or ignore it
			 */
Alan Rogers committed
1081
			if (contains_oitem)
1082
				return iterator_advance(&info->nitem, info->new_iter);
1083
			delta_type = GIT_DELTA_IGNORED;
1084
		}
1085 1086 1087
	}

	else if (delta_type == GIT_DELTA_IGNORED &&
1088 1089
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
		git_iterator_current_tree_is_ignored(info->new_iter))
1090
		/* item contained in ignored directory, so skip over it */
1091
		return iterator_advance(&info->nitem, info->new_iter);
1092

1093 1094 1095 1096
	else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) {
		if (delta_type != GIT_DELTA_CONFLICTED)
			delta_type = GIT_DELTA_ADDED;
	}
1097

1098 1099
	else if (nitem->mode == GIT_FILEMODE_COMMIT) {
		/* ignore things that are not actual submodules */
1100
		if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
1101 1102
			giterr_clear();
			delta_type = GIT_DELTA_IGNORED;
1103 1104 1105

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

				giterr_clear();
1111
				return iterator_advance(&info->nitem, info->new_iter);
1112
			}
1113 1114 1115
		}
	}

1116
	else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
1117 1118 1119 1120
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
			delta_type = GIT_DELTA_UNTRACKED;
		else
			delta_type = GIT_DELTA_UNREADABLE;
1121 1122
	}

1123
	/* Actually create the record for this item if necessary */
1124
	if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
1125 1126
		return error;

1127 1128
	/* If user requested TYPECHANGE records, then check for that instead of
	 * just generating an ADDED/UNTRACKED record
1129 1130 1131
	 */
	if (delta_type != GIT_DELTA_IGNORED &&
		DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
1132
		contains_oitem)
1133 1134 1135 1136 1137 1138 1139 1140 1141
	{
		/* 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;
		}
	}

1142
	return iterator_advance(&info->nitem, info->new_iter);
1143 1144 1145
}

static int handle_unmatched_old_item(
1146
	git_diff *diff, diff_in_progress *info)
1147
{
1148 1149 1150 1151
	git_delta_t delta_type = GIT_DELTA_DELETED;
	int error;

	/* update delta_type if this item is conflicted */
1152
	if (git_index_entry_is_conflict(info->oitem))
1153 1154
		delta_type = GIT_DELTA_CONFLICTED;

1155
	if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
		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))
1177
			return iterator_advance(&info->nitem, info->new_iter);
1178 1179
	}

1180
	return iterator_advance(&info->oitem, info->old_iter);
1181 1182 1183
}

static int handle_matched_item(
1184
	git_diff *diff, diff_in_progress *info)
1185 1186 1187
{
	int error = 0;

1188 1189 1190
	if ((error = maybe_modified(diff, info)) < 0)
		return error;

1191 1192
	if (!(error = iterator_advance(&info->oitem, info->old_iter)))
		error = iterator_advance(&info->nitem, info->new_iter);
1193 1194 1195 1196

	return error;
}

Russell Belfer committed
1197
int git_diff__from_iterators(
1198
	git_diff **diff_ptr,
1199
	git_repository *repo,
1200 1201
	git_iterator *old_iter,
	git_iterator *new_iter,
1202
	const git_diff_options *opts)
1203
{
1204
	int error = 0;
1205
	diff_in_progress info;
1206
	git_diff *diff;
1207 1208 1209 1210

	*diff_ptr = NULL;

	diff = diff_list_alloc(repo, old_iter, new_iter);
1211
	GITERR_CHECK_ALLOC(diff);
1212

1213 1214 1215 1216
	info.repo = repo;
	info.old_iter = old_iter;
	info.new_iter = new_iter;

1217
	/* make iterators have matching icase behavior */
Russell Belfer committed
1218
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
1219 1220 1221
		if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
			(error = git_iterator_set_ignore_case(new_iter, true)) < 0)
			goto cleanup;
1222 1223
	}

1224
	/* finish initialization */
1225 1226 1227
	if ((error = diff_list_apply_options(diff, opts)) < 0)
		goto cleanup;

1228 1229
	if ((error = iterator_current(&info.oitem, old_iter)) < 0 ||
		(error = iterator_current(&info.nitem, new_iter)) < 0)
1230
		goto cleanup;
1231

1232
	/* run iterators building diffs */
1233 1234 1235
	while (!error && (info.oitem || info.nitem)) {
		int cmp = info.oitem ?
			(info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
1236

1237
		/* create DELETED records for old items not matched in new */
1238 1239
		if (cmp < 0)
			error = handle_unmatched_old_item(diff, &info);
1240

1241 1242 1243
		/* create ADDED, TRACKED, or IGNORED records for new items not
		 * matched in old (and/or descend into directories as needed)
		 */
1244 1245
		else if (cmp > 0)
			error = handle_unmatched_new_item(diff, &info);
1246

1247 1248
		/* otherwise item paths match, so create MODIFIED record
		 * (or ADDED and DELETED pair if type changed)
1249
		 */
1250 1251
		else
			error = handle_matched_item(diff, &info);
1252 1253
	}

1254 1255
	diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;

1256
cleanup:
1257 1258 1259
	if (!error)
		*diff_ptr = diff;
	else
1260
		git_diff_free(diff);
1261 1262

	return error;
1263 1264
}

1265
#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
1266 1267
	git_iterator *a = NULL, *b = NULL; \
	char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
Ben Straub committed
1268
	GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
1269
	if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
Russell Belfer committed
1270
		error = git_diff__from_iterators(diff, repo, a, b, opts); \
1271
	git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
1272
} while (0)
1273

1274
int git_diff_tree_to_tree(
1275
	git_diff **diff,
1276
	git_repository *repo,
1277 1278
	git_tree *old_tree,
	git_tree *new_tree,
1279
	const git_diff_options *opts)
1280
{
1281
	int error = 0;
1282
	git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
1283 1284 1285

	assert(diff && repo);

1286 1287 1288 1289
	/* 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
1290
	if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
1291 1292
		iflag = GIT_ITERATOR_IGNORE_CASE;

1293
	DIFF_FROM_ITERATORS(
1294 1295
		git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
		git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
1296
	);
1297

1298
	return error;
1299 1300
}

1301 1302 1303 1304 1305
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 */
1306
	if (!error && git_index_read(*index, false) < 0)
1307 1308 1309 1310 1311
		giterr_clear();

	return error;
}

1312
int git_diff_tree_to_index(
1313
	git_diff **diff,
1314
	git_repository *repo,
1315
	git_tree *old_tree,
1316
	git_index *index,
1317
	const git_diff_options *opts)
1318
{
1319
	int error = 0;
1320
	bool index_ignore_case = false;
1321 1322
	git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
		GIT_ITERATOR_INCLUDE_CONFLICTS;
1323 1324 1325

	assert(diff && repo);

1326
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1327 1328
		return error;

1329
	index_ignore_case = index->ignore_case;
1330

1331
	DIFF_FROM_ITERATORS(
1332 1333
		git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
		git_iterator_for_index(&b, index, iflag, pfx, pfx)
1334
	);
1335

1336 1337 1338
	/* if index is in case-insensitive order, re-sort deltas to match */
	if (!error && index_ignore_case)
		diff_set_ignore_case(*diff, true);
1339

1340
	return error;
1341 1342
}

1343
int git_diff_index_to_workdir(
1344
	git_diff **diff,
1345
	git_repository *repo,
1346
	git_index *index,
1347
	const git_diff_options *opts)
1348
{
1349 1350 1351 1352
	int error = 0;

	assert(diff && repo);

1353
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1354 1355
		return error;

1356
	DIFF_FROM_ITERATORS(
1357 1358
		git_iterator_for_index(
			&a, index, GIT_ITERATOR_INCLUDE_CONFLICTS, pfx, pfx),
1359
		git_iterator_for_workdir(
1360
			&b, repo, index, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1361
	);
1362

1363 1364 1365
	if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX))
		error = git_index_write(index);

1366
	return error;
1367 1368
}

1369
int git_diff_tree_to_workdir(
1370
	git_diff **diff,
1371
	git_repository *repo,
1372 1373
	git_tree *old_tree,
	const git_diff_options *opts)
1374
{
1375
	int error = 0;
1376
	git_index *index;
1377 1378 1379

	assert(diff && repo);

Carlos Martín Nieto committed
1380
	if ((error = git_repository_index__weakptr(&index, repo)))
1381 1382
		return error;

1383
	DIFF_FROM_ITERATORS(
1384
		git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
1385
		git_iterator_for_workdir(
1386
			&b, repo, index, old_tree, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1387
	);
1388 1389

	return error;
1390
}
1391

Russell Belfer committed
1392 1393 1394 1395 1396 1397 1398 1399
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;
1400
	git_index *index = NULL;
Russell Belfer committed
1401 1402 1403

	assert(diff && repo);

1404 1405 1406 1407 1408
	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
1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
		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)
1423 1424
{
	assert(diff);
Russell Belfer committed
1425
	return diff->deltas.length;
1426 1427
}

Russell Belfer committed
1428
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
1429 1430
{
	size_t i, count = 0;
Russell Belfer committed
1431
	const git_diff_delta *delta;
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441

	assert(diff);

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

	return count;
}

Russell Belfer committed
1442 1443 1444 1445 1446 1447
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
{
	assert(diff);
	return git_vector_get(&diff->deltas, idx);
}

1448
int git_diff_is_sorted_icase(const git_diff *diff)
1449
{
Russell Belfer committed
1450
	return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1451 1452
}

1453 1454
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
{
1455 1456
	assert(out);
	GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
1457 1458 1459 1460 1461
	out->stat_calls = diff->perf.stat_calls;
	out->oid_calculations = diff->perf.oid_calculations;
	return 0;
}

1462
int git_diff__paired_foreach(
1463 1464
	git_diff *head2idx,
	git_diff *idx2wd,
1465
	int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
1466 1467
	void *payload)
{
1468
	int cmp, error = 0;
1469
	git_diff_delta *h2i, *i2w;
1470
	size_t i, j, i_max, j_max;
1471
	int (*strcomp)(const char *, const char *) = git__strcmp;
1472
	bool h2i_icase, i2w_icase, icase_mismatch;
1473

1474 1475
	i_max = head2idx ? head2idx->deltas.length : 0;
	j_max = idx2wd ? idx2wd->deltas.length : 0;
1476 1477
	if (!i_max && !j_max)
		return 0;
1478

1479 1480 1481 1482 1483 1484 1485 1486
	/* 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.
1487 1488 1489 1490
	 *
	 * 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.
1491
	 */
1492
	h2i_icase = head2idx != NULL &&
Russell Belfer committed
1493
		(head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1494 1495

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

1498
	icase_mismatch =
1499 1500 1501 1502 1503
		(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);
1504
	}
1505 1506

	if (i2w_icase && !icase_mismatch) {
1507
		strcomp = git__strcasecmp;
1508

1509 1510 1511 1512 1513 1514 1515
		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);
	}

1516
	for (i = 0, j = 0; i < i_max || j < j_max; ) {
1517 1518
		h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
		i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
1519

1520 1521
		cmp = !i2w ? -1 : !h2i ? 1 :
			strcomp(h2i->new_file.path, i2w->old_file.path);
1522 1523

		if (cmp < 0) {
1524
			i++; i2w = NULL;
1525
		} else if (cmp > 0) {
1526
			j++; h2i = NULL;
1527 1528 1529
		} else {
			i++; j++;
		}
1530

1531
		if ((error = cb(h2i, i2w, payload)) != 0) {
1532
			giterr_set_after_callback(error);
1533
			break;
1534
		}
1535 1536
	}

1537
	/* restore case-insensitive delta sort */
1538 1539 1540 1541 1542 1543 1544 1545 1546 1547
	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);
1548 1549
	}

1550
	return error;
1551
}
1552

1553 1554 1555 1556 1557 1558 1559 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 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669
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;
1670
	char *summary = NULL, *loc = NULL;
1671 1672
	bool ignore_marker;
	unsigned int format_flags = 0;
1673
	size_t allocsize;
1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
	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;
		}
	}

1693 1694 1695 1696 1697 1698 1699 1700 1701
	/* 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;
1702
			goto on_error;
1703 1704
		}

1705 1706
		GITERR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
		summary = git__calloc(allocsize, sizeof(char));
1707
		GITERR_CHECK_ALLOC(summary);
1708

1709 1710 1711
		strncpy(summary, opts->summary, offset);
	}

1712
	error = git_diff_format_email__append_header_tobuf(out,
1713
				opts->id, opts->author, summary == NULL ? opts->summary : summary,
1714 1715 1716 1717 1718 1719 1720 1721 1722
				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 ||
1723 1724
		(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
		(error = git_buf_putc(out, '\n')) < 0 ||
1725 1726 1727 1728 1729 1730
		(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:
1731
	git__free(summary);
1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767
	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;
}

1768
int git_diff_init_options(git_diff_options *opts, unsigned int version)
1769
{
1770 1771
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
1772
	return 0;
1773 1774
}

1775 1776
int git_diff_find_init_options(
	git_diff_find_options *opts, unsigned int version)
1777
{
1778 1779
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
1780
	return 0;
1781
}
1782

1783 1784
int git_diff_format_email_init_options(
	git_diff_format_email_options *opts, unsigned int version)
1785
{
1786 1787 1788
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_format_email_options,
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
1789
	return 0;
1790
}