diff.c 43.7 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
		return 0;
95 96 97 98
	
	if (status == GIT_DELTA_UNREADABLE &&
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
		return 0;
Russell Belfer committed
99

100
	if (!git_pathspec__match(
101
			&diff->pathspec, entry->path,
102
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
103
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
104
			&matched_pathspec, NULL))
105 106
		return 0;

Russell Belfer committed
107
	delta = diff_delta__alloc(diff, status, entry->path);
108
	GITERR_CHECK_ALLOC(delta);
109

110
	/* This fn is just for single-sided diffs */
111
	assert(status != GIT_DELTA_MODIFIED);
112
	delta->nfiles = 1;
113

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

124
	delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
125 126

	if (delta->status == GIT_DELTA_DELETED ||
127 128
		!git_oid_iszero(&delta->new_file.id))
		delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
129

130
	return diff_insert_delta(diff, delta, matched_pathspec);
131 132
}

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

Russell Belfer committed
146
	if (status == GIT_DELTA_UNMODIFIED &&
147
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
Russell Belfer committed
148 149
		return 0;

150
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
151 152
		uint32_t temp_mode = old_mode;
		const git_index_entry *temp_entry = old_entry;
153
		old_entry = new_entry;
154 155 156
		new_entry = temp_entry;
		old_mode = new_mode;
		new_mode = temp_mode;
157
	}
158

159
	delta = diff_delta__alloc(diff, status, canonical_path);
160
	GITERR_CHECK_ALLOC(delta);
161
	delta->nfiles = 2;
162

163
	git_oid_cpy(&delta->old_file.id, &old_entry->id);
164 165
	delta->old_file.size = old_entry->file_size;
	delta->old_file.mode = old_mode;
166
	delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
167

168
	git_oid_cpy(&delta->new_file.id, &new_entry->id);
169 170
	delta->new_file.size = new_entry->file_size;
	delta->new_file.mode = new_mode;
171 172

	if (new_oid) {
173
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
174
			git_oid_cpy(&delta->old_file.id, new_oid);
175
		else
176
			git_oid_cpy(&delta->new_file.id, new_oid);
177 178
	}

179
	if (new_oid || !git_oid_iszero(&new_entry->id))
180
		delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
181

182
	return diff_insert_delta(diff, delta, matched_pathspec);
183 184
}

185
static git_diff_delta *diff_delta__last_for_item(
186
	git_diff *diff,
187 188 189 190 191 192 193 194 195
	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:
196
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
197 198 199
			return delta;
		break;
	case GIT_DELTA_ADDED:
200
		if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
201 202
			return delta;
		break;
203
	case GIT_DELTA_UNREADABLE:
204
	case GIT_DELTA_UNTRACKED:
205 206 207 208
		if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
			return delta;
		break;
209
	case GIT_DELTA_MODIFIED:
210 211
		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
212 213 214 215 216 217 218 219 220
			return delta;
		break;
	default:
		break;
	}

	return NULL;
}

221
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
222 223
{
	size_t len = strlen(prefix);
224 225 226 227 228 229

	/* 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);
230 231
}

232 233 234 235 236 237 238 239 240 241 242 243 244
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;
}

245 246 247 248 249
const char *git_diff_delta__path(const git_diff_delta *delta)
{
	return diff_delta__path(delta);
}

250
int git_diff_delta__cmp(const void *a, const void *b)
251 252
{
	const git_diff_delta *da = a, *db = b;
253
	int val = strcmp(diff_delta__path(da), diff_delta__path(db));
254 255 256
	return val ? val : ((int)da->status - (int)db->status);
}

257 258 259 260 261 262 263
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);
}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
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);
}

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
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;

301 302 303 304
	if (delta->status == GIT_DELTA_UNREADABLE &&
		(flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
		return true;

305 306 307 308
	return false;
}


309 310
static const char *diff_mnemonic_prefix(
	git_iterator_type_t type, bool left_side)
311
{
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	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;
}

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
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);
}

355
static git_diff *diff_list_alloc(
356 357 358 359 360
	git_repository *repo,
	git_iterator *old_iter,
	git_iterator *new_iter)
{
	git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
361
	git_diff *diff = git__calloc(1, sizeof(git_diff));
362
	if (!diff)
363 364
		return NULL;

365 366
	assert(repo && old_iter && new_iter);

Russell Belfer committed
367
	GIT_REFCOUNT_INC(diff);
368
	diff->repo = repo;
369 370 371
	diff->old_src = old_iter->type;
	diff->new_src = new_iter->type;
	memcpy(&diff->opts, &dflt, sizeof(diff->opts));
372

373
	if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
374
		git_pool_init(&diff->pool, 1, 0) < 0) {
375
		git_diff_free(diff);
376 377 378 379 380
		return NULL;
	}

	/* Use case-insensitive compare if either iterator has
	 * the ignore_case bit set */
381 382 383 384
	diff_set_ignore_case(
		diff,
		git_iterator_ignore_case(old_iter) ||
		git_iterator_ignore_case(new_iter));
385 386 387 388 389

	return diff;
}

static int diff_list_apply_options(
390
	git_diff *diff,
391 392
	const git_diff_options *opts)
{
393
	git_config *cfg = NULL;
394 395 396 397 398 399
	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
400
		bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
401
		memcpy(&diff->opts, opts, sizeof(diff->opts));
Russell Belfer committed
402
		DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
403 404

		/* initialize pathspec from options */
405
		if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
406 407 408 409 410 411
			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;
412

413
	/* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
Russell Belfer committed
414
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
415 416
		diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;

417
	/* load config values that affect diff behavior */
418
	if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
419
		return val;
420

421
	if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
422
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
423

424
	if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
425
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT;
426 427

	if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
428
		!git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
429
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
430

431
	if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
432
		diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
433

434 435
	/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */

436 437 438
	/* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
	diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;

439 440
	/* If not given explicit `opts`, check `diff.xyz` configs */
	if (!opts) {
441
		int context = git_config__get_int_force(cfg, "diff.context", 3);
Linquize committed
442
		diff->opts.context_lines = context >= 0 ? (uint16_t)context : 3;
443

444 445 446
		/* add other defaults here */
	}

447 448 449 450 451 452 453
	/* 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;
	}

454 455 456 457 458 459 460 461
	/* 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;

462 463
	/* if ignore_submodules not explicitly set, check diff config */
	if (diff->opts.ignore_submodules <= 0) {
464 465
		const git_config_entry *entry;
		git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
466

467 468
		if (entry && git_submodule_parse_ignore(
				&diff->opts.ignore_submodules, entry->value) < 0)
469
			giterr_clear();
470
	}
471

472 473 474 475
	/* 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;
476

477
		if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
478
			use_old = use_new = "";
479
		else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
480 481 482
			use_old = diff_mnemonic_prefix(diff->old_src, true);
			use_new = diff_mnemonic_prefix(diff->new_src, false);
		}
483

484 485 486 487 488
		if (!diff->opts.old_prefix)
			diff->opts.old_prefix = use_old;
		if (!diff->opts.new_prefix)
			diff->opts.new_prefix = use_new;
	}
489

490 491 492
	/* 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);
493

494
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
495 496 497
		const char *tmp_prefix = diff->opts.old_prefix;
		diff->opts.old_prefix  = diff->opts.new_prefix;
		diff->opts.new_prefix  = tmp_prefix;
498
	}
499

500 501 502 503
	git_config_free(cfg);

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

506
static void diff_list_free(git_diff *diff)
507
{
508
	git_vector_free_deep(&diff->deltas);
509

510
	git_pathspec__vfree(&diff->pathspec);
511
	git_pool_clear(&diff->pool);
512

513
	git__memzero(diff, sizeof(*diff));
514 515 516
	git__free(diff);
}

517
void git_diff_free(git_diff *diff)
Russell Belfer committed
518 519 520 521 522 523 524
{
	if (!diff)
		return;

	GIT_REFCOUNT_DEC(diff, diff_list_free);
}

525
void git_diff_addref(git_diff *diff)
526 527 528 529
{
	GIT_REFCOUNT_INC(diff);
}

530
int git_diff__oid_for_file(
531 532
	git_oid *out,
	git_diff *diff,
533 534
	const char *path,
	uint16_t  mode,
535
	git_off_t size)
536
{
537 538 539 540 541 542 543
	git_index_entry entry;

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

544
	return git_diff__oid_for_entry(out, diff, &entry, NULL);
545 546 547
}

int git_diff__oid_for_entry(
548 549 550 551
	git_oid *out,
	git_diff *diff,
	const git_index_entry *src,
	const git_oid *update_match)
552
{
553
	int error = 0;
554
	git_buf full_path = GIT_BUF_INIT;
555
	git_index_entry entry = *src;
556 557 558
	git_filter_list *fl = NULL;

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

560
	if (git_buf_joinpath(
561
		&full_path, git_repository_workdir(diff->repo), entry.path) < 0)
562
		return -1;
563

564
	if (!entry.mode) {
565 566
		struct stat st;

567 568
		diff->perf.stat_calls++;

569
		if (p_stat(full_path.ptr, &st) < 0) {
570
			error = git_path_set_error(errno, entry.path, "stat");
571 572
			git_buf_free(&full_path);
			return error;
573 574
		}

575 576
		git_index_entry__init_from_stat(
			&entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
577 578
	}

579
	/* calculate OID for file if possible */
580
	if (S_ISGITLINK(entry.mode)) {
581 582
		git_submodule *sm;

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

		git_filter_list_free(fl);
616 617
	}

618 619 620 621 622 623 624 625 626 627
	/* 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);
		}
 	}
628

629
	git_buf_free(&full_path);
630
	return error;
631 632
}

633 634 635
static bool diff_time_eq(
	const git_index_time *a, const git_index_time *b, bool use_nanos)
{
636
	return a->seconds == b->seconds &&
637 638 639
		(!use_nanos || a->nanoseconds == b->nanoseconds);
}

640 641 642 643 644 645 646 647
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;

648
#define MODE_BITS_MASK 0000777
649

650 651 652
static int maybe_modified_submodule(
	git_delta_t *status,
	git_oid *found_oid,
653
	git_diff *diff,
654 655 656 657 658
	diff_in_progress *info)
{
	int error = 0;
	git_submodule *sub;
	unsigned int sm_status = 0;
659
	git_submodule_ignore_t ign = diff->opts.ignore_submodules;
660 661 662

	*status = GIT_DELTA_UNMODIFIED;

663 664 665
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
		ign == GIT_SUBMODULE_IGNORE_ALL)
		return 0;
666

667 668 669 670 671 672 673 674 675
	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;
676 677
	}

678
	if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
679 680
		/* ignore it */;
	else if ((error = git_submodule__status(
681
			&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
682
		/* return error below */;
683 684 685 686

	/* check IS_WD_UNMODIFIED because this case is only used
	 * when the new side of the diff is the working directory
	 */
687
	else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
688 689 690
		*status = GIT_DELTA_MODIFIED;

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

695 696
	git_submodule_free(sub);
	return error;
697 698
}

699
static int maybe_modified(
700
	git_diff *diff,
701
	diff_in_progress *info)
702
{
703
	git_oid noid;
Russell Belfer committed
704
	git_delta_t status = GIT_DELTA_MODIFIED;
705 706
	const git_index_entry *oitem = info->oitem;
	const git_index_entry *nitem = info->nitem;
707 708
	unsigned int omode = oitem->mode;
	unsigned int nmode = nitem->mode;
709
	bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
710
	bool modified_uncertain = false;
711
	const char *matched_pathspec;
712
	int error = 0;
713

714
	if (!git_pathspec__match(
715
			&diff->pathspec, oitem->path,
716
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
Russell Belfer committed
717
			DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
718
			&matched_pathspec, NULL))
719 720
		return 0;

721 722
	memset(&noid, 0, sizeof(noid));

723
	/* on platforms with no symlinks, preserve mode of existing symlinks */
724 725
	if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
		!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
726
		nmode = omode;
727

728 729 730
	/* 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) &&
731
		new_is_workdir)
732
		nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
733

734
	/* support "assume unchanged" (poorly, b/c we still stat everything) */
735 736
	if ((oitem->flags & GIT_IDXENTRY_VALID) != 0)
		status = GIT_DELTA_UNMODIFIED;
737 738 739

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

Russell Belfer committed
742
	/* if basic type of file changed, then split into delete and add */
743
	else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
744
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
745
			status = GIT_DELTA_TYPECHANGE;
746 747 748 749 750
		else if (nmode == GIT_FILEMODE_UNREADABLE) {
			if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
				error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, nitem);
			return error;
		}
751
		else {
752 753 754
			if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
				error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem);
			return error;
755
		}
756 757
	}

758
	/* if oids and modes match (and are valid), then file is unmodified */
759
	else if (git_oid_equal(&oitem->id, &nitem->id) &&
760
			 omode == nmode &&
761
			 !git_oid_iszero(&oitem->id))
Russell Belfer committed
762
		status = GIT_DELTA_UNMODIFIED;
763

764 765
	/* if we have an unknown OID and a workdir iterator, then check some
	 * circumstances that can accelerate things or need special handling
766
	 */
767
	else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
768 769
		bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
		bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
770

771
		status = GIT_DELTA_UNMODIFIED;
Russell Belfer committed
772

773 774 775
		/* TODO: add check against index file st_mtime to avoid racy-git */

		if (S_ISGITLINK(nmode)) {
776 777
			if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
				return error;
778
		}
779 780 781 782

		/* if the stat data looks different, then mark modified - this just
		 * means that the OID will be recalculated below to confirm change
		 */
783 784 785 786 787 788
		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) ||
789 790 791 792 793
			(use_ctime &&
			 !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
			oitem->ino != nitem->ino ||
			oitem->uid != nitem->uid ||
			oitem->gid != nitem->gid)
794
		{
795
			status = GIT_DELTA_MODIFIED;
796 797
			modified_uncertain = true;
		}
798
	}
799

800 801 802 803 804
	/* 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;

805 806 807
	/* 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
	 */
808
	if (modified_uncertain && git_oid_iszero(&nitem->id)) {
809
		if (git_oid_iszero(&noid)) {
810 811 812 813 814 815
			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)
816
				return error;
817
		}
818 819 820 821 822 823

		/* 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) &&
824
			git_oid_equal(&oitem->id, &noid))
Russell Belfer committed
825
			status = GIT_DELTA_UNMODIFIED;
826 827
	}

828
	return diff_delta__from_two(
829 830
		diff, status, oitem, omode, nitem, nmode,
		git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
831 832
}

833
static bool entry_is_prefixed(
834
	git_diff *diff,
835 836 837 838 839
	const git_index_entry *item,
	const git_index_entry *prefix_item)
{
	size_t pathlen;

840
	if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
841 842
		return false;

843
	pathlen = strlen(prefix_item->path);
844

845 846 847
	return (prefix_item->path[pathlen - 1] == '/' ||
			item->path[pathlen] == '\0' ||
			item->path[pathlen] == '/');
848 849
}

850
static int handle_unmatched_new_item(
851
	git_diff *diff, diff_in_progress *info)
852 853 854 855
{
	int error = 0;
	const git_index_entry *nitem = info->nitem;
	git_delta_t delta_type = GIT_DELTA_UNTRACKED;
856
	bool contains_oitem;
857

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

861 862 863
	/* update delta_type if this item is ignored */
	if (git_iterator_current_is_ignored(info->new_iter))
		delta_type = GIT_DELTA_IGNORED;
864

865
	if (nitem->mode == GIT_FILEMODE_TREE) {
866 867 868 869 870 871 872 873 874 875
		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 */
876
		if (recurse_into_dir && !contains_oitem) {
877 878 879
			git_buf *full = NULL;
			if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
				return -1;
880 881
			if (full && git_path_contains(full, DOT_GIT)) {
				/* TODO: warning if not a valid git repository */
882
				recurse_into_dir = false;
883
			}
884
		}
885

886 887 888 889 890
		/* 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
891
			DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
892 893
		{
			git_diff_delta *last;
894
			git_iterator_status_t untracked_state;
895 896

			/* attempt to insert record for this directory */
897
			if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
898 899 900 901 902 903 904 905
				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 */
906 907
			if ((error = git_iterator_advance_over_with_status(
					&info->nitem, &untracked_state, info->new_iter)) < 0 &&
908 909
				error != GIT_ITEROVER)
				return error;
910

911 912 913
			/* if we found nothing or just ignored items, update the record */
			if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
				untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
				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);

930 931 932 933
			/* if real error or no error, proceed with iteration */
			if (error != GIT_ENOTFOUND)
				return error;
			giterr_clear();
934

935 936 937
			/* if directory is empty, can't advance into it, so either skip
			 * it or ignore it
			 */
Alan Rogers committed
938
			if (contains_oitem)
939 940
				return git_iterator_advance(&info->nitem, info->new_iter);
			delta_type = GIT_DELTA_IGNORED;
941
		}
942 943 944
	}

	else if (delta_type == GIT_DELTA_IGNORED &&
945 946
		DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
		git_iterator_current_tree_is_ignored(info->new_iter))
947
		/* item contained in ignored directory, so skip over it */
948 949 950 951 952
		return git_iterator_advance(&info->nitem, info->new_iter);

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

953 954
	else if (nitem->mode == GIT_FILEMODE_COMMIT) {
		/* ignore things that are not actual submodules */
955
		if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
956 957
			giterr_clear();
			delta_type = GIT_DELTA_IGNORED;
958 959 960 961 962 963 964 965 966 967

			/* 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);
			}
968 969 970
		}
	}

971
	else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
972 973 974 975
		if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
			delta_type = GIT_DELTA_UNTRACKED;
		else
			delta_type = GIT_DELTA_UNREADABLE;
976 977
	}

978
	/* Actually create the record for this item if necessary */
979
	if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
980 981
		return error;

982 983
	/* If user requested TYPECHANGE records, then check for that instead of
	 * just generating an ADDED/UNTRACKED record
984 985 986
	 */
	if (delta_type != GIT_DELTA_IGNORED &&
		DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
987
		contains_oitem)
988 989 990 991 992 993 994 995 996 997 998 999 1000
	{
		/* 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(
1001
	git_diff *diff, diff_in_progress *info)
1002 1003
{
	int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem);
1004
	if (error != 0)
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
		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(
1033
	git_diff *diff, diff_in_progress *info)
1034 1035 1036
{
	int error = 0;

1037 1038 1039 1040 1041
	if ((error = maybe_modified(diff, info)) < 0)
		return error;

	if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) ||
		error == GIT_ITEROVER)
1042 1043 1044 1045 1046
		error = git_iterator_advance(&info->nitem, info->new_iter);

	return error;
}

Russell Belfer committed
1047
int git_diff__from_iterators(
1048
	git_diff **diff_ptr,
1049
	git_repository *repo,
1050 1051
	git_iterator *old_iter,
	git_iterator *new_iter,
1052
	const git_diff_options *opts)
1053
{
1054
	int error = 0;
1055
	diff_in_progress info;
1056
	git_diff *diff;
1057 1058 1059 1060

	*diff_ptr = NULL;

	diff = diff_list_alloc(repo, old_iter, new_iter);
1061
	GITERR_CHECK_ALLOC(diff);
1062

1063 1064 1065 1066
	info.repo = repo;
	info.old_iter = old_iter;
	info.new_iter = new_iter;

1067
	/* make iterators have matching icase behavior */
Russell Belfer committed
1068
	if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
1069 1070 1071
		if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
			(error = git_iterator_set_ignore_case(new_iter, true)) < 0)
			goto cleanup;
1072 1073
	}

1074
	/* finish initialization */
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
	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;
1085

1086
	/* run iterators building diffs */
1087 1088 1089
	while (!error && (info.oitem || info.nitem)) {
		int cmp = info.oitem ?
			(info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
1090

1091
		/* create DELETED records for old items not matched in new */
1092 1093
		if (cmp < 0)
			error = handle_unmatched_old_item(diff, &info);
1094

1095 1096 1097
		/* create ADDED, TRACKED, or IGNORED records for new items not
		 * matched in old (and/or descend into directories as needed)
		 */
1098 1099
		else if (cmp > 0)
			error = handle_unmatched_new_item(diff, &info);
1100

1101 1102
		/* otherwise item paths match, so create MODIFIED record
		 * (or ADDED and DELETED pair if type changed)
1103
		 */
1104 1105
		else
			error = handle_matched_item(diff, &info);
1106 1107 1108 1109

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

1112 1113
	diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;

1114
cleanup:
1115 1116 1117
	if (!error)
		*diff_ptr = diff;
	else
1118
		git_diff_free(diff);
1119 1120

	return error;
1121 1122
}

1123
#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
1124 1125
	git_iterator *a = NULL, *b = NULL; \
	char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
Ben Straub committed
1126
	GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
1127
	if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
Russell Belfer committed
1128
		error = git_diff__from_iterators(diff, repo, a, b, opts); \
1129
	git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
1130
} while (0)
1131

1132
int git_diff_tree_to_tree(
1133
	git_diff **diff,
1134
	git_repository *repo,
1135 1136
	git_tree *old_tree,
	git_tree *new_tree,
1137
	const git_diff_options *opts)
1138
{
1139
	int error = 0;
1140
	git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
1141 1142 1143

	assert(diff && repo);

1144 1145 1146 1147
	/* 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
1148
	if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
1149 1150
		iflag = GIT_ITERATOR_IGNORE_CASE;

1151
	DIFF_FROM_ITERATORS(
1152 1153
		git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
		git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
1154
	);
1155

1156
	return error;
1157 1158
}

1159 1160 1161 1162 1163
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 */
1164
	if (!error && git_index_read(*index, false) < 0)
1165 1166 1167 1168 1169
		giterr_clear();

	return error;
}

1170
int git_diff_tree_to_index(
1171
	git_diff **diff,
1172
	git_repository *repo,
1173
	git_tree *old_tree,
1174
	git_index *index,
1175
	const git_diff_options *opts)
1176
{
1177
	int error = 0;
1178
	bool index_ignore_case = false;
1179 1180 1181

	assert(diff && repo);

1182
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1183 1184
		return error;

1185
	index_ignore_case = index->ignore_case;
1186

1187
	DIFF_FROM_ITERATORS(
1188 1189 1190 1191
		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)
1192
	);
1193

1194 1195 1196
	/* if index is in case-insensitive order, re-sort deltas to match */
	if (!error && index_ignore_case)
		diff_set_ignore_case(*diff, true);
1197

1198
	return error;
1199 1200
}

1201
int git_diff_index_to_workdir(
1202
	git_diff **diff,
1203
	git_repository *repo,
1204
	git_index *index,
1205
	const git_diff_options *opts)
1206
{
1207 1208 1209 1210
	int error = 0;

	assert(diff && repo);

1211
	if (!index && (error = diff_load_index(&index, repo)) < 0)
1212 1213
		return error;

1214
	DIFF_FROM_ITERATORS(
1215
		git_iterator_for_index(&a, index, 0, pfx, pfx),
1216 1217
		git_iterator_for_workdir(
			&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1218
	);
1219

1220 1221 1222
	if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX))
		error = git_index_write(index);

1223
	return error;
1224 1225
}

1226
int git_diff_tree_to_workdir(
1227
	git_diff **diff,
1228
	git_repository *repo,
1229 1230
	git_tree *old_tree,
	const git_diff_options *opts)
1231
{
1232 1233 1234 1235
	int error = 0;

	assert(diff && repo);

1236
	DIFF_FROM_ITERATORS(
1237
		git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
1238 1239
		git_iterator_for_workdir(
			&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
1240
	);
1241 1242

	return error;
1243
}
1244

Russell Belfer committed
1245 1246 1247 1248 1249 1250 1251 1252
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;
1253
	git_index *index = NULL;
Russell Belfer committed
1254 1255 1256

	assert(diff && repo);

1257 1258 1259 1260 1261
	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
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275
		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)
1276 1277
{
	assert(diff);
Russell Belfer committed
1278
	return diff->deltas.length;
1279 1280
}

Russell Belfer committed
1281
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
1282 1283
{
	size_t i, count = 0;
Russell Belfer committed
1284
	const git_diff_delta *delta;
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294

	assert(diff);

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

	return count;
}

Russell Belfer committed
1295 1296 1297 1298 1299 1300
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
{
	assert(diff);
	return git_vector_get(&diff->deltas, idx);
}

1301
int git_diff_is_sorted_icase(const git_diff *diff)
1302
{
Russell Belfer committed
1303
	return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1304 1305
}

1306 1307
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
{
1308 1309
	assert(out);
	GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
1310 1311 1312 1313 1314
	out->stat_calls = diff->perf.stat_calls;
	out->oid_calculations = diff->perf.oid_calculations;
	return 0;
}

1315
int git_diff__paired_foreach(
1316 1317
	git_diff *head2idx,
	git_diff *idx2wd,
1318
	int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
1319 1320
	void *payload)
{
1321
	int cmp, error = 0;
1322
	git_diff_delta *h2i, *i2w;
1323
	size_t i, j, i_max, j_max;
1324
	int (*strcomp)(const char *, const char *) = git__strcmp;
1325
	bool h2i_icase, i2w_icase, icase_mismatch;
1326

1327 1328
	i_max = head2idx ? head2idx->deltas.length : 0;
	j_max = idx2wd ? idx2wd->deltas.length : 0;
1329 1330
	if (!i_max && !j_max)
		return 0;
1331

1332 1333 1334 1335 1336 1337 1338 1339
	/* 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.
1340 1341 1342 1343
	 *
	 * 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.
1344
	 */
1345
	h2i_icase = head2idx != NULL &&
Russell Belfer committed
1346
		(head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
1347 1348

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

1351
	icase_mismatch =
1352 1353 1354 1355 1356
		(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);
1357
	}
1358 1359

	if (i2w_icase && !icase_mismatch) {
1360
		strcomp = git__strcasecmp;
1361

1362 1363 1364 1365 1366 1367 1368
		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);
	}

1369
	for (i = 0, j = 0; i < i_max || j < j_max; ) {
1370 1371
		h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
		i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
1372

1373 1374
		cmp = !i2w ? -1 : !h2i ? 1 :
			strcomp(h2i->new_file.path, i2w->old_file.path);
1375 1376

		if (cmp < 0) {
1377
			i++; i2w = NULL;
1378
		} else if (cmp > 0) {
1379
			j++; h2i = NULL;
1380 1381 1382
		} else {
			i++; j++;
		}
1383

1384
		if ((error = cb(h2i, i2w, payload)) != 0) {
1385
			giterr_set_after_callback(error);
1386
			break;
1387
		}
1388 1389
	}

1390
	/* restore case-insensitive delta sort */
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
	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);
1401 1402
	}

1403
	return error;
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 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522
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;
1523
	char *summary = NULL, *loc = NULL;
1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
	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;
		}
	}

1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560
	/* 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);
	}

1561
	error = git_diff_format_email__append_header_tobuf(out,
1562
				opts->id, opts->author, summary == NULL ? opts->summary : summary,
1563 1564 1565 1566 1567 1568 1569 1570 1571
				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 ||
1572 1573
		(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
		(error = git_buf_putc(out, '\n')) < 0 ||
1574 1575 1576 1577 1578 1579
		(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:
1580
	git__free(summary);
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
	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;
}

1617
int git_diff_init_options(git_diff_options *opts, unsigned int version)
1618
{
1619 1620
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
1621
	return 0;
1622 1623
}

1624 1625
int git_diff_find_init_options(
	git_diff_find_options *opts, unsigned int version)
1626
{
1627 1628
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
1629
	return 0;
1630
}
1631

1632 1633
int git_diff_format_email_init_options(
	git_diff_format_email_options *opts, unsigned int version)
1634
{
1635 1636 1637
	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
		opts, version, git_diff_format_email_options,
		GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
1638
	return 0;
1639
}