diff.c 187 KB
Newer Older
Junio C Hamano's avatar
Junio C Hamano committed
1 2 3 4
/*
 * Copyright (C) 2005 Junio C Hamano
 */
#include "cache.h"
5
#include "config.h"
6
#include "tempfile.h"
Junio C Hamano's avatar
Junio C Hamano committed
7 8 9
#include "quote.h"
#include "diff.h"
#include "diffcore.h"
Junio C Hamano's avatar
Junio C Hamano committed
10
#include "delta.h"
Junio C Hamano's avatar
Junio C Hamano committed
11
#include "xdiff-interface.h"
12
#include "color.h"
13
#include "attr.h"
14
#include "run-command.h"
15
#include "utf8.h"
16
#include "object-store.h"
17
#include "userdiff.h"
18
#include "submodule-config.h"
19
#include "submodule.h"
20
#include "hashmap.h"
21
#include "ll-merge.h"
22
#include "string-list.h"
23
#include "argv-array.h"
24
#include "graph.h"
25
#include "packfile.h"
26
#include "parse-options.h"
27
#include "help.h"
28
#include "fetch-object.h"
Junio C Hamano's avatar
Junio C Hamano committed
29

30 31 32 33 34 35
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
#else
#define FAST_WORKING_DIRECTORY 1
#endif

36
static int diff_detect_rename_default;
37
static int diff_indent_heuristic = 1;
38
static int diff_rename_limit_default = 400;
39
static int diff_suppress_blank_empty;
40
static int diff_use_color_default = -1;
41
static int diff_color_moved_default;
42
static int diff_color_moved_ws_default;
43
static int diff_context_default = 3;
44
static int diff_interhunk_context_default;
45
static const char *diff_word_regex_cfg;
46
static const char *external_diff_cmd_cfg;
47
static const char *diff_order_file_cfg;
48
int diff_auto_refresh_index = 1;
49
static int diff_mnemonic_prefix;
50
static int diff_no_prefix;
51
static int diff_stat_graph_width;
52
static int diff_dirstat_permille_default = 30;
53
static struct diff_options default_diff_options;
54
static long diff_algorithm;
55
static unsigned ws_error_highlight_default = WSEH_NEW;
Junio C Hamano's avatar
Junio C Hamano committed
56

57
static char diff_colors[][COLOR_MAXLEN] = {
58
	GIT_COLOR_RESET,
59
	GIT_COLOR_NORMAL,	/* CONTEXT */
60 61 62 63 64 65
	GIT_COLOR_BOLD,		/* METAINFO */
	GIT_COLOR_CYAN,		/* FRAGINFO */
	GIT_COLOR_RED,		/* OLD */
	GIT_COLOR_GREEN,	/* NEW */
	GIT_COLOR_YELLOW,	/* COMMIT */
	GIT_COLOR_BG_RED,	/* WHITESPACE */
66
	GIT_COLOR_NORMAL,	/* FUNCINFO */
67 68 69 70 71 72 73 74
	GIT_COLOR_BOLD_MAGENTA,	/* OLD_MOVED */
	GIT_COLOR_BOLD_BLUE,	/* OLD_MOVED ALTERNATIVE */
	GIT_COLOR_FAINT,	/* OLD_MOVED_DIM */
	GIT_COLOR_FAINT_ITALIC,	/* OLD_MOVED_ALTERNATIVE_DIM */
	GIT_COLOR_BOLD_CYAN,	/* NEW_MOVED */
	GIT_COLOR_BOLD_YELLOW,	/* NEW_MOVED ALTERNATIVE */
	GIT_COLOR_FAINT,	/* NEW_MOVED_DIM */
	GIT_COLOR_FAINT_ITALIC,	/* NEW_MOVED_ALTERNATIVE_DIM */
75 76 77 78 79 80
	GIT_COLOR_FAINT,	/* CONTEXT_DIM */
	GIT_COLOR_FAINT_RED,	/* OLD_DIM */
	GIT_COLOR_FAINT_GREEN,	/* NEW_DIM */
	GIT_COLOR_BOLD,		/* CONTEXT_BOLD */
	GIT_COLOR_BOLD_RED,	/* OLD_BOLD */
	GIT_COLOR_BOLD_GREEN,	/* NEW_BOLD */
81 82
};

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
static const char *color_diff_slots[] = {
	[DIFF_CONTEXT]		      = "context",
	[DIFF_METAINFO]		      = "meta",
	[DIFF_FRAGINFO]		      = "frag",
	[DIFF_FILE_OLD]		      = "old",
	[DIFF_FILE_NEW]		      = "new",
	[DIFF_COMMIT]		      = "commit",
	[DIFF_WHITESPACE]	      = "whitespace",
	[DIFF_FUNCINFO]		      = "func",
	[DIFF_FILE_OLD_MOVED]	      = "oldMoved",
	[DIFF_FILE_OLD_MOVED_ALT]     = "oldMovedAlternative",
	[DIFF_FILE_OLD_MOVED_DIM]     = "oldMovedDimmed",
	[DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed",
	[DIFF_FILE_NEW_MOVED]	      = "newMoved",
	[DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
	[DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
	[DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
100 101 102 103 104 105
	[DIFF_CONTEXT_DIM]	      = "contextDimmed",
	[DIFF_FILE_OLD_DIM]	      = "oldDimmed",
	[DIFF_FILE_NEW_DIM]	      = "newDimmed",
	[DIFF_CONTEXT_BOLD]	      = "contextBold",
	[DIFF_FILE_OLD_BOLD]	      = "oldBold",
	[DIFF_FILE_NEW_BOLD]	      = "newBold",
106 107
};

108 109
define_list_config_array_extra(color_diff_slots, {"plain"});

110
static int parse_diff_color_slot(const char *var)
111
{
112
	if (!strcasecmp(var, "plain"))
113
		return DIFF_CONTEXT;
114
	return LOOKUP_CONFIG(color_diff_slots, var);
115 116
}

117
static int parse_dirstat_params(struct diff_options *options, const char *params_string,
118
				struct strbuf *errmsg)
119
{
120 121 122 123
	char *params_copy = xstrdup(params_string);
	struct string_list params = STRING_LIST_INIT_NODUP;
	int ret = 0;
	int i;
124

125 126 127 128 129
	if (*params_copy)
		string_list_split_in_place(&params, params_copy, ',', -1);
	for (i = 0; i < params.nr; i++) {
		const char *p = params.items[i].string;
		if (!strcmp(p, "changes")) {
130 131
			options->flags.dirstat_by_line = 0;
			options->flags.dirstat_by_file = 0;
132
		} else if (!strcmp(p, "lines")) {
133 134
			options->flags.dirstat_by_line = 1;
			options->flags.dirstat_by_file = 0;
135
		} else if (!strcmp(p, "files")) {
136 137
			options->flags.dirstat_by_line = 0;
			options->flags.dirstat_by_file = 1;
138
		} else if (!strcmp(p, "noncumulative")) {
139
			options->flags.dirstat_cumulative = 0;
140
		} else if (!strcmp(p, "cumulative")) {
141
			options->flags.dirstat_cumulative = 1;
142 143
		} else if (isdigit(*p)) {
			char *end;
144 145
			int permille = strtoul(p, &end, 10) * 10;
			if (*end == '.' && isdigit(*++end)) {
146
				/* only use first digit */
147
				permille += *end - '0';
148
				/* .. and ignore any further digits */
149
				while (isdigit(*++end))
150 151
					; /* nothing */
			}
152
			if (!*end)
153 154
				options->dirstat_permille = permille;
			else {
155 156
				strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%s'\n"),
					    p);
157 158 159
				ret++;
			}
		} else {
160
			strbuf_addf(errmsg, _("  Unknown dirstat parameter '%s'\n"), p);
161
			ret++;
162
		}
163

164
	}
165 166
	string_list_clear(&params, 0);
	free(params_copy);
167
	return ret;
168 169
}

170 171 172
static int parse_submodule_params(struct diff_options *options, const char *value)
{
	if (!strcmp(value, "log"))
173
		options->submodule_format = DIFF_SUBMODULE_LOG;
174
	else if (!strcmp(value, "short"))
175
		options->submodule_format = DIFF_SUBMODULE_SHORT;
176 177
	else if (!strcmp(value, "diff"))
		options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
178 179 180 181
	/*
	 * Please update $__git_diff_submodule_formats in
	 * git-completion.bash when you add new formats.
	 */
182 183 184 185 186
	else
		return -1;
	return 0;
}

187
int git_config_rename(const char *var, const char *value)
188 189 190 191 192 193 194 195
{
	if (!value)
		return DIFF_DETECT_RENAME;
	if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy"))
		return  DIFF_DETECT_COPY;
	return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
}

196
long parse_algorithm_value(const char *value)
197 198 199 200 201 202 203 204 205 206 207
{
	if (!value)
		return -1;
	else if (!strcasecmp(value, "myers") || !strcasecmp(value, "default"))
		return 0;
	else if (!strcasecmp(value, "minimal"))
		return XDF_NEED_MINIMAL;
	else if (!strcasecmp(value, "patience"))
		return XDF_PATIENCE_DIFF;
	else if (!strcasecmp(value, "histogram"))
		return XDF_HISTOGRAM_DIFF;
208 209 210 211
	/*
	 * Please update $__git_diff_algorithms in git-completion.bash
	 * when you add new algorithms.
	 */
212 213 214
	return -1;
}

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
static int parse_one_token(const char **arg, const char *token)
{
	const char *rest;
	if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
		*arg = rest;
		return 1;
	}
	return 0;
}

static int parse_ws_error_highlight(const char *arg)
{
	const char *orig_arg = arg;
	unsigned val = 0;

	while (*arg) {
		if (parse_one_token(&arg, "none"))
			val = 0;
		else if (parse_one_token(&arg, "default"))
			val = WSEH_NEW;
		else if (parse_one_token(&arg, "all"))
			val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
		else if (parse_one_token(&arg, "new"))
			val |= WSEH_NEW;
		else if (parse_one_token(&arg, "old"))
			val |= WSEH_OLD;
		else if (parse_one_token(&arg, "context"))
			val |= WSEH_CONTEXT;
		else {
			return -1 - (int)(arg - orig_arg);
		}
		if (*arg)
			arg++;
	}
	return val;
}

252 253 254 255 256 257
/*
 * These are to give UI layer defaults.
 * The core-level commands such as git-diff-files should
 * never be affected by the setting of diff.renames
 * the user happens to have in the configuration file.
 */
258 259
void init_diff_ui_defaults(void)
{
260
	diff_detect_rename_default = DIFF_DETECT_RENAME;
261 262
}

263 264
int git_diff_heuristic_config(const char *var, const char *value, void *cb)
{
265
	if (!strcmp(var, "diff.indentheuristic"))
266 267 268 269
		diff_indent_heuristic = git_config_bool(var, value);
	return 0;
}

270 271 272 273 274 275 276 277 278 279 280 281 282
static int parse_color_moved(const char *arg)
{
	switch (git_parse_maybe_bool(arg)) {
	case 0:
		return COLOR_MOVED_NO;
	case 1:
		return COLOR_MOVED_DEFAULT;
	default:
		break;
	}

	if (!strcmp(arg, "no"))
		return COLOR_MOVED_NO;
283 284
	else if (!strcmp(arg, "plain"))
		return COLOR_MOVED_PLAIN;
285 286
	else if (!strcmp(arg, "blocks"))
		return COLOR_MOVED_BLOCKS;
287 288 289 290
	else if (!strcmp(arg, "zebra"))
		return COLOR_MOVED_ZEBRA;
	else if (!strcmp(arg, "default"))
		return COLOR_MOVED_DEFAULT;
291 292
	else if (!strcmp(arg, "dimmed-zebra"))
		return COLOR_MOVED_ZEBRA_DIM;
293 294
	else if (!strcmp(arg, "dimmed_zebra"))
		return COLOR_MOVED_ZEBRA_DIM;
295
	else
296
		return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed-zebra', 'plain'"));
297 298
}

299
static unsigned parse_color_moved_ws(const char *arg)
300 301 302 303 304 305 306 307 308 309 310 311
{
	int ret = 0;
	struct string_list l = STRING_LIST_INIT_DUP;
	struct string_list_item *i;

	string_list_split(&l, arg, ',', -1);

	for_each_string_list_item(i, &l) {
		struct strbuf sb = STRBUF_INIT;
		strbuf_addstr(&sb, i->string);
		strbuf_trim(&sb);

312 313 314
		if (!strcmp(sb.buf, "no"))
			ret = 0;
		else if (!strcmp(sb.buf, "ignore-space-change"))
315 316 317 318 319
			ret |= XDF_IGNORE_WHITESPACE_CHANGE;
		else if (!strcmp(sb.buf, "ignore-space-at-eol"))
			ret |= XDF_IGNORE_WHITESPACE_AT_EOL;
		else if (!strcmp(sb.buf, "ignore-all-space"))
			ret |= XDF_IGNORE_WHITESPACE;
320 321
		else if (!strcmp(sb.buf, "allow-indentation-change"))
			ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;
322 323 324 325
		else {
			ret |= COLOR_MOVED_WS_ERROR;
			error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), sb.buf);
		}
326 327 328 329

		strbuf_release(&sb);
	}

330
	if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&
331
	    (ret & XDF_WHITESPACE_FLAGS)) {
332
		error(_("color-moved-ws: allow-indentation-change cannot be combined with other whitespace modes"));
333 334
		ret |= COLOR_MOVED_WS_ERROR;
	}
335

336 337 338
	string_list_clear(&l, 0);

	return ret;
339 340
}

341
int git_diff_ui_config(const char *var, const char *value, void *cb)
342
{
343
	if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
344
		diff_use_color_default = git_config_colorbool(var, value);
345 346
		return 0;
	}
347 348 349 350 351 352 353
	if (!strcmp(var, "diff.colormoved")) {
		int cm = parse_color_moved(value);
		if (cm < 0)
			return -1;
		diff_color_moved_default = cm;
		return 0;
	}
354
	if (!strcmp(var, "diff.colormovedws")) {
355 356
		unsigned cm = parse_color_moved_ws(value);
		if (cm & COLOR_MOVED_WS_ERROR)
357 358 359 360
			return -1;
		diff_color_moved_ws_default = cm;
		return 0;
	}
361 362 363 364 365 366
	if (!strcmp(var, "diff.context")) {
		diff_context_default = git_config_int(var, value);
		if (diff_context_default < 0)
			return -1;
		return 0;
	}
367 368 369 370 371 372
	if (!strcmp(var, "diff.interhunkcontext")) {
		diff_interhunk_context_default = git_config_int(var, value);
		if (diff_interhunk_context_default < 0)
			return -1;
		return 0;
	}
373
	if (!strcmp(var, "diff.renames")) {
374
		diff_detect_rename_default = git_config_rename(var, value);
375 376
		return 0;
	}
377 378 379 380
	if (!strcmp(var, "diff.autorefreshindex")) {
		diff_auto_refresh_index = git_config_bool(var, value);
		return 0;
	}
381 382 383 384
	if (!strcmp(var, "diff.mnemonicprefix")) {
		diff_mnemonic_prefix = git_config_bool(var, value);
		return 0;
	}
385 386 387 388
	if (!strcmp(var, "diff.noprefix")) {
		diff_no_prefix = git_config_bool(var, value);
		return 0;
	}
389 390 391 392
	if (!strcmp(var, "diff.statgraphwidth")) {
		diff_stat_graph_width = git_config_int(var, value);
		return 0;
	}
393 394
	if (!strcmp(var, "diff.external"))
		return git_config_string(&external_diff_cmd_cfg, var, value);
395 396
	if (!strcmp(var, "diff.wordregex"))
		return git_config_string(&diff_word_regex_cfg, var, value);
397 398
	if (!strcmp(var, "diff.orderfile"))
		return git_config_pathname(&diff_order_file_cfg, var, value);
399

400 401 402
	if (!strcmp(var, "diff.ignoresubmodules"))
		handle_ignore_submodules_arg(&default_diff_options, value);

403 404 405 406 407 408 409
	if (!strcmp(var, "diff.submodule")) {
		if (parse_submodule_params(&default_diff_options, value))
			warning(_("Unknown value for 'diff.submodule' config variable: '%s'"),
				value);
		return 0;
	}

410 411 412 413 414 415 416
	if (!strcmp(var, "diff.algorithm")) {
		diff_algorithm = parse_algorithm_value(value);
		if (diff_algorithm < 0)
			return -1;
		return 0;
	}

417 418 419 420 421 422 423 424
	if (!strcmp(var, "diff.wserrorhighlight")) {
		int val = parse_ws_error_highlight(value);
		if (val < 0)
			return -1;
		ws_error_highlight_default = val;
		return 0;
	}

425 426 427
	if (git_color_config(var, value, cb) < 0)
		return -1;

428
	return git_diff_basic_config(var, value, cb);
429 430
}

431
int git_diff_basic_config(const char *var, const char *value, void *cb)
432
{
433 434
	const char *name;

435 436 437 438 439
	if (!strcmp(var, "diff.renamelimit")) {
		diff_rename_limit_default = git_config_int(var, value);
		return 0;
	}

440 441
	if (userdiff_config(var, value) < 0)
		return -1;
442

443 444 445
	if (skip_prefix(var, "diff.color.", &name) ||
	    skip_prefix(var, "color.diff.", &name)) {
		int slot = parse_diff_color_slot(name);
446 447
		if (slot < 0)
			return 0;
448 449
		if (!value)
			return config_error_nonbool(var);
450
		return color_parse(value, diff_colors[slot]);
451
	}
452

453
	/* like GNU diff's --suppress-blank-empty option  */
454 455 456
	if (!strcmp(var, "diff.suppressblankempty") ||
			/* for backwards compatibility */
			!strcmp(var, "diff.suppress-blank-empty")) {
457 458 459 460
		diff_suppress_blank_empty = git_config_bool(var, value);
		return 0;
	}

461
	if (!strcmp(var, "diff.dirstat")) {
462
		struct strbuf errmsg = STRBUF_INIT;
463
		default_diff_options.dirstat_permille = diff_dirstat_permille_default;
464
		if (parse_dirstat_params(&default_diff_options, value, &errmsg))
465
			warning(_("Found errors in 'diff.dirstat' config variable:\n%s"),
466 467
				errmsg.buf);
		strbuf_release(&errmsg);
468
		diff_dirstat_permille_default = default_diff_options.dirstat_permille;
469 470 471
		return 0;
	}

472 473 474
	if (git_diff_heuristic_config(var, value, cb) < 0)
		return -1;

475
	return git_default_config(var, value, cb);
476 477
}

Junio C Hamano's avatar
Junio C Hamano committed
478 479 480 481
static char *quote_two(const char *one, const char *two)
{
	int need_one = quote_c_style(one, NULL, NULL, 1);
	int need_two = quote_c_style(two, NULL, NULL, 1);
482
	struct strbuf res = STRBUF_INIT;
Junio C Hamano's avatar
Junio C Hamano committed
483 484

	if (need_one + need_two) {
485 486 487 488 489 490 491
		strbuf_addch(&res, '"');
		quote_c_style(one, &res, NULL, 1);
		quote_c_style(two, &res, NULL, 1);
		strbuf_addch(&res, '"');
	} else {
		strbuf_addstr(&res, one);
		strbuf_addstr(&res, two);
Junio C Hamano's avatar
Junio C Hamano committed
492
	}
493
	return strbuf_detach(&res, NULL);
Junio C Hamano's avatar
Junio C Hamano committed
494 495 496 497 498 499 500 501 502
}

static const char *external_diff(void)
{
	static const char *external_diff_cmd = NULL;
	static int done_preparing = 0;

	if (done_preparing)
		return external_diff_cmd;
503
	external_diff_cmd = xstrdup_or_null(getenv("GIT_EXTERNAL_DIFF"));
504 505
	if (!external_diff_cmd)
		external_diff_cmd = external_diff_cmd_cfg;
Junio C Hamano's avatar
Junio C Hamano committed
506 507 508 509
	done_preparing = 1;
	return external_diff_cmd;
}

510 511 512 513 514
/*
 * Keep track of files used for diffing. Sometimes such an entry
 * refers to a temporary file, sometimes to an existing file, and
 * sometimes to "/dev/null".
 */
Junio C Hamano's avatar
Junio C Hamano committed
515
static struct diff_tempfile {
516 517 518 519 520 521
	/*
	 * filename external diff should read from, or NULL if this
	 * entry is currently not in use:
	 */
	const char *name;

522
	char hex[GIT_MAX_HEXSZ + 1];
Junio C Hamano's avatar
Junio C Hamano committed
523
	char mode[10];
524 525 526 527 528

	/*
	 * If this diff_tempfile instance refers to a temporary file,
	 * this tempfile object is used to manage its lifetime.
	 */
529
	struct tempfile *tempfile;
Junio C Hamano's avatar
Junio C Hamano committed
530 531
} diff_temp[2];

532 533 534 535 536 537 538 539 540
struct emit_callback {
	int color_diff;
	unsigned ws_rule;
	int blank_at_eof_in_preimage;
	int blank_at_eof_in_postimage;
	int lno_in_preimage;
	int lno_in_postimage;
	const char **label_path;
	struct diff_words_data *diff_words;
541
	struct diff_options *opt;
542
	struct strbuf *header;
543 544
};

Junio C Hamano's avatar
Junio C Hamano committed
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
static int count_lines(const char *data, int size)
{
	int count, ch, completely_empty = 1, nl_just_seen = 0;
	count = 0;
	while (0 < size--) {
		ch = *data++;
		if (ch == '\n') {
			count++;
			nl_just_seen = 1;
			completely_empty = 0;
		}
		else {
			nl_just_seen = 0;
			completely_empty = 0;
		}
	}
	if (completely_empty)
		return 0;
	if (!nl_just_seen)
		count++; /* no trailing newline */
	return count;
}

568 569
static int fill_mmfile(struct repository *r, mmfile_t *mf,
		       struct diff_filespec *one)
570 571 572 573 574 575
{
	if (!DIFF_FILE_VALID(one)) {
		mf->ptr = (char *)""; /* does not matter */
		mf->size = 0;
		return 0;
	}
576
	else if (diff_populate_filespec(r, one, 0))
577
		return -1;
578

579 580 581 582 583
	mf->ptr = one->data;
	mf->size = one->size;
	return 0;
}

584
/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
585 586
static unsigned long diff_filespec_size(struct repository *r,
					struct diff_filespec *one)
587 588 589
{
	if (!DIFF_FILE_VALID(one))
		return 0;
590
	diff_populate_filespec(r, one, CHECK_SIZE_ONLY);
591 592 593
	return one->size;
}

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
{
	char *ptr = mf->ptr;
	long size = mf->size;
	int cnt = 0;

	if (!size)
		return cnt;
	ptr += size - 1; /* pointing at the very end */
	if (*ptr != '\n')
		; /* incomplete line */
	else
		ptr--; /* skip the last LF */
	while (mf->ptr < ptr) {
		char *prev_eol;
		for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--)
			if (*prev_eol == '\n')
				break;
		if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule))
			break;
		cnt++;
		ptr = prev_eol - 1;
	}
	return cnt;
}

static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
			       struct emit_callback *ecbdata)
{
	int l1, l2, at;
	unsigned ws_rule = ecbdata->ws_rule;
	l1 = count_trailing_blank(mf1, ws_rule);
	l2 = count_trailing_blank(mf2, ws_rule);
	if (l2 <= l1) {
		ecbdata->blank_at_eof_in_preimage = 0;
		ecbdata->blank_at_eof_in_postimage = 0;
		return;
	}
	at = count_lines(mf1->ptr, mf1->size);
	ecbdata->blank_at_eof_in_preimage = (at - l1) + 1;

	at = count_lines(mf2->ptr, mf2->size);
	ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
}

639
static void emit_line_0(struct diff_options *o,
640
			const char *set_sign, const char *set, unsigned reverse, const char *reset,
641
			int first, const char *line, int len)
642 643
{
	int has_trailing_newline, has_trailing_carriage_return;
644
	int needs_reset = 0; /* at the end of the line */
645 646
	FILE *file = o->file;

647
	fputs(diff_line_prefix(o), file);
648

649 650 651 652 653 654 655 656 657 658 659 660 661 662
	has_trailing_newline = (len > 0 && line[len-1] == '\n');
	if (has_trailing_newline)
		len--;

	has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
	if (has_trailing_carriage_return)
		len--;

	if (!len && !first)
		goto end_of_line;

	if (reverse && want_color(o->use_color)) {
		fputs(GIT_COLOR_REVERSE, file);
		needs_reset = 1;
663
	}
664

665 666 667
	if (set_sign) {
		fputs(set_sign, file);
		needs_reset = 1;
668
	}
669 670 671 672 673 674 675 676 677 678

	if (first)
		fputc(first, file);

	if (!len)
		goto end_of_line;

	if (set) {
		if (set_sign && set != set_sign)
			fputs(reset, file);
679
		fputs(set, file);
680
		needs_reset = 1;
681
	}
682 683 684 685 686 687
	fwrite(line, len, 1, file);
	needs_reset = 1; /* 'line' may contain color codes. */

end_of_line:
	if (needs_reset)
		fputs(reset, file);
688 689 690 691 692 693
	if (has_trailing_carriage_return)
		fputc('\r', file);
	if (has_trailing_newline)
		fputc('\n', file);
}

694
static void emit_line(struct diff_options *o, const char *set, const char *reset,
695 696
		      const char *line, int len)
{
697
	emit_line_0(o, set, NULL, 0, reset, 0, line, len);
698 699
}

700
enum diff_symbol {
701 702 703 704 705
	DIFF_SYMBOL_BINARY_DIFF_HEADER,
	DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA,
	DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL,
	DIFF_SYMBOL_BINARY_DIFF_BODY,
	DIFF_SYMBOL_BINARY_DIFF_FOOTER,
706 707 708 709
	DIFF_SYMBOL_STATS_SUMMARY_NO_FILES,
	DIFF_SYMBOL_STATS_SUMMARY_ABBREV,
	DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES,
	DIFF_SYMBOL_STATS_LINE,
710
	DIFF_SYMBOL_WORD_DIFF,
711
	DIFF_SYMBOL_STAT_SEP,
712
	DIFF_SYMBOL_SUMMARY,
713 714 715 716 717 718 719
	DIFF_SYMBOL_SUBMODULE_ADD,
	DIFF_SYMBOL_SUBMODULE_DEL,
	DIFF_SYMBOL_SUBMODULE_UNTRACKED,
	DIFF_SYMBOL_SUBMODULE_MODIFIED,
	DIFF_SYMBOL_SUBMODULE_HEADER,
	DIFF_SYMBOL_SUBMODULE_ERROR,
	DIFF_SYMBOL_SUBMODULE_PIPETHROUGH,
720
	DIFF_SYMBOL_REWRITE_DIFF,
721
	DIFF_SYMBOL_BINARY_FILES,
722
	DIFF_SYMBOL_HEADER,
723 724
	DIFF_SYMBOL_FILEPAIR_PLUS,
	DIFF_SYMBOL_FILEPAIR_MINUS,
725 726
	DIFF_SYMBOL_WORDS_PORCELAIN,
	DIFF_SYMBOL_WORDS,
727
	DIFF_SYMBOL_CONTEXT,
728
	DIFF_SYMBOL_CONTEXT_INCOMPLETE,
729 730
	DIFF_SYMBOL_PLUS,
	DIFF_SYMBOL_MINUS,
731
	DIFF_SYMBOL_NO_LF_EOF,
732
	DIFF_SYMBOL_CONTEXT_FRAGINFO,
733
	DIFF_SYMBOL_CONTEXT_MARKER,
734 735
	DIFF_SYMBOL_SEPARATOR
};
736 737 738 739 740 741
/*
 * Flags for content lines:
 * 0..12 are whitespace rules
 * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
 * 16 is marking if the line is blank at EOF
 */
742 743 744
#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF	(1<<16)
#define DIFF_SYMBOL_MOVED_LINE			(1<<17)
#define DIFF_SYMBOL_MOVED_LINE_ALT		(1<<18)
745
#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING	(1<<19)
746 747
#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)

748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
/*
 * This struct is used when we need to buffer the output of the diff output.
 *
 * NEEDSWORK: Instead of storing a copy of the line, add an offset pointer
 * into the pre/post image file. This pointer could be a union with the
 * line pointer. By storing an offset into the file instead of the literal line,
 * we can decrease the memory footprint for the buffered output. At first we
 * may want to only have indirection for the content lines, but we could also
 * enhance the state for emitting prefabricated lines, e.g. the similarity
 * score line or hunk/file headers would only need to store a number or path
 * and then the output can be constructed later on depending on state.
 */
struct emitted_diff_symbol {
	const char *line;
	int len;
	int flags;
764 765
	int indent_off;   /* Offset to first non-whitespace character */
	int indent_width; /* The visual width of the indentation */
766 767 768 769 770 771 772 773 774 775 776 777
	enum diff_symbol s;
};
#define EMITTED_DIFF_SYMBOL_INIT {NULL}

struct emitted_diff_symbols {
	struct emitted_diff_symbol *buf;
	int nr, alloc;
};
#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}

static void append_emitted_diff_symbol(struct diff_options *o,
				       struct emitted_diff_symbol *e)
778
{
779 780 781 782 783 784 785 786 787
	struct emitted_diff_symbol *f;

	ALLOC_GROW(o->emitted_symbols->buf,
		   o->emitted_symbols->nr + 1,
		   o->emitted_symbols->alloc);
	f = &o->emitted_symbols->buf[o->emitted_symbols->nr++];

	memcpy(f, e, sizeof(struct emitted_diff_symbol));
	f->line = e->line ? xmemdupz(e->line, e->len) : NULL;
788 789
}

790 791 792 793 794 795
struct moved_entry {
	struct hashmap_entry ent;
	const struct emitted_diff_symbol *es;
	struct moved_entry *next_line;
};

796 797
struct moved_block {
	struct moved_entry *match;
798
	int wsd; /* The whitespace delta of this block */
799 800 801 802
};

static void moved_block_clear(struct moved_block *b)
{
803
	memset(b, 0, sizeof(*b));
804 805
}

806 807
#define INDENT_BLANKLINE INT_MIN

808
static void fill_es_indent_data(struct emitted_diff_symbol *es)
809
{
810
	unsigned int off = 0, i;
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
	int width = 0, tab_width = es->flags & WS_TAB_WIDTH_MASK;
	const char *s = es->line;
	const int len = es->len;

	/* skip any \v \f \r at start of indentation */
	while (s[off] == '\f' || s[off] == '\v' ||
	       (s[off] == '\r' && off < len - 1))
		off++;

	/* calculate the visual width of indentation */
	while(1) {
		if (s[off] == ' ') {
			width++;
			off++;
		} else if (s[off] == '\t') {
			width += tab_width - (width % tab_width);
			while (s[++off] == '\t')
				width += tab_width;
		} else {
			break;
		}
	}

834 835 836 837
	/* check if this line is blank */
	for (i = off; i < len; i++)
		if (!isspace(s[i]))
		    break;
838

839 840 841 842 843 844 845
	if (i == len) {
		es->indent_width = INDENT_BLANKLINE;
		es->indent_off = len;
	} else {
		es->indent_off = off;
		es->indent_width = width;
	}
846 847
}

848
static int compute_ws_delta(const struct emitted_diff_symbol *a,
849 850
			    const struct emitted_diff_symbol *b,
			    int *out)
851
{
852 853 854 855 856 857 858 859
	int a_len = a->len,
	    b_len = b->len,
	    a_off = a->indent_off,
	    a_width = a->indent_width,
	    b_off = b->indent_off,
	    b_width = b->indent_width;
	int delta;

860 861 862 863 864
	if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE) {
		*out = INDENT_BLANKLINE;
		return 1;
	}

865 866 867 868
	if (a->s == DIFF_SYMBOL_PLUS)
		delta = a_width - b_width;
	else
		delta = b_width - a_width;
869

870 871
	if (a_len - a_off != b_len - b_off ||
	    memcmp(a->line + a_off, b->line + b_off, a_len - a_off))
872 873
		return 0;

874
	*out = delta;
875

876
	return 1;
877 878 879 880 881
}

static int cmp_in_block_with_wsd(const struct diff_options *o,
				 const struct moved_entry *cur,
				 const struct moved_entry *match,
882
				 struct moved_block *pmb,
883 884 885
				 int n)
{
	struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
886
	int al = cur->es->len, bl = match->es->len, cl = l->len;
887 888 889
	const char *a = cur->es->line,
		   *b = match->es->line,
		   *c = l->line;
890 891 892 893 894
	int a_off = cur->es->indent_off,
	    a_width = cur->es->indent_width,
	    c_off = l->indent_off,
	    c_width = l->indent_width;
	int delta;
895 896

	/*
897 898 899 900 901 902 903
	 * We need to check if 'cur' is equal to 'match'.  As those
	 * are from the same (+/-) side, we do not need to adjust for
	 * indent changes. However these were found using fuzzy
	 * matching so we do have to check if they are equal. Here we
	 * just check the lengths. We delay calling memcmp() to check
	 * the contents until later as if the length comparison for a
	 * and c fails we can avoid the call all together.
904
	 */
905
	if (al != bl)
906 907
		return 1;

908 909 910
	/* If 'l' and 'cur' are both blank then they match. */
	if (a_width == INDENT_BLANKLINE && c_width == INDENT_BLANKLINE)
		return 0;
911 912

	/*
913 914 915 916
	 * The indent changes of the block are known and stored in pmb->wsd;
	 * however we need to check if the indent changes of the current line
	 * match those of the current block and that the text of 'l' and 'cur'
	 * after the indentation match.
917
	 */
918 919 920 921
	if (cur->es->s == DIFF_SYMBOL_PLUS)
		delta = a_width - c_width;
	else
		delta = c_width - a_width;
922

923 924 925 926 927 928
	/*
	 * If the previous lines of this block were all blank then set its
	 * whitespace delta.
	 */
	if (pmb->wsd == INDENT_BLANKLINE)
		pmb->wsd = delta;
929

930 931 932
	return !(delta == pmb->wsd && al - a_off == cl - c_off &&
		 !memcmp(a, b, al) && !
		 memcmp(a + a_off, c + c_off, al - a_off));
933
}
934

935 936 937
static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
			   const void *entry,
			   const void *entry_or_key,
938 939
			   const void *keydata)
{
940 941 942
	const struct diff_options *diffopt = hashmap_cmp_fn_data;
	const struct moved_entry *a = entry;
	const struct moved_entry *b = entry_or_key;
943 944
	unsigned flags = diffopt->color_moved_ws_handling
			 & XDF_WHITESPACE_FLAGS;
945

946 947 948 949 950 951 952 953 954 955
	if (diffopt->color_moved_ws_handling &
	    COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
		/*
		 * As there is not specific white space config given,
		 * we'd need to check for a new block, so ignore all
		 * white space. The setup of the white space
		 * configuration for the next block is done else where
		 */
		flags |= XDF_IGNORE_WHITESPACE;

956 957
	return !xdiff_compare_lines(a->es->line, a->es->len,
				    b->es->line, b->es->len,
958
				    flags);
959 960 961 962 963 964 965
}

static struct moved_entry *prepare_entry(struct diff_options *o,
					 int line_no)
{
	struct moved_entry *ret = xmalloc(sizeof(*ret));
	struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
966
	unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS;
967

968
	ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
	ret->es = l;
	ret->next_line = NULL;

	return ret;
}

static void add_lines_to_move_detection(struct diff_options *o,
					struct hashmap *add_lines,
					struct hashmap *del_lines)
{
	struct moved_entry *prev_line = NULL;

	int n;
	for (n = 0; n < o->emitted_symbols->nr; n++) {
		struct hashmap *hm;
		struct moved_entry *key;

		switch (o->emitted_symbols->buf[n].s) {
		case DIFF_SYMBOL_PLUS:
			hm = add_lines;
			break;
		case DIFF_SYMBOL_MINUS:
			hm = del_lines;
			break;
		default:
			prev_line = NULL;
			continue;
		}

998 999 1000
		if (o->color_moved_ws_handling &
		    COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
			fill_es_indent_data(&o->emitted_symbols->buf[n]);
1001 1002 1003 1004 1005 1006 1007 1008 1009
		key = prepare_entry(o, n);
		if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
			prev_line->next_line = key;

		hashmap_add(hm, key);
		prev_line = key;
	}
}

1010 1011 1012
static void pmb_advance_or_null(struct diff_options *o,
				struct moved_entry *match,
				struct hashmap *hm,
1013
				struct moved_block *pmb,
1014 1015 1016 1017
				int pmb_nr)
{
	int i;
	for (i = 0; i < pmb_nr; i++) {
1018
		struct moved_entry *prev = pmb[i].match;
1019 1020 1021
		struct moved_entry *cur = (prev && prev->next_line) ?
				prev->next_line : NULL;
		if (cur && !hm->cmpfn(o, cur, match, NULL)) {
1022
			pmb[i].match = cur;
1023
		} else {
1024
			pmb[i].match = NULL;
1025 1026 1027 1028
		}
	}
}

1029 1030 1031
static void pmb_advance_or_null_multi_match(struct diff_options *o,
					    struct moved_entry *match,
					    struct hashmap *hm,
1032
					    struct moved_block *pmb,
1033 1034 1035 1036 1037 1038 1039
					    int pmb_nr, int n)
{
	int i;
	char *got_match = xcalloc(1, pmb_nr);

	for (; match; match = hashmap_get_next(hm, match)) {
		for (i = 0; i < pmb_nr; i++) {
1040
			struct moved_entry *prev = pmb[i].match;
1041 1042 1043 1044
			struct moved_entry *cur = (prev && prev->next_line) ?
					prev->next_line : NULL;
			if (!cur)
				continue;
1045
			if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n))
1046 1047 1048 1049 1050 1051
				got_match[i] |= 1;
		}
	}

	for (i = 0; i < pmb_nr; i++) {
		if (got_match[i]) {
1052 1053
			/* Advance to the next line */
			pmb[i].match = pmb[i].match->next_line;
1054
		} else {
1055
			moved_block_clear(&pmb[i]);
1056
		}
1057
	}
1058 1059

	free(got_match);
1060 1061
}

1062
static int shrink_potential_moved_blocks(struct moved_block *pmb,
1063 1064 1065 1066 1067 1068
					 int pmb_nr)
{
	int lp, rp;

	/* Shrink the set of potential block to the remaining running */
	for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
1069
		while (lp < pmb_nr && pmb[lp].match)
1070 1071 1072
			lp++;
		/* lp points at the first NULL now */

1073
		while (rp > -1 && !pmb[rp].match)
1074 1075 1076 1077 1078
			rp--;
		/* rp points at the last non-NULL */

		if (lp < pmb_nr && rp > -1 && lp < rp) {
			pmb[lp] = pmb[rp];
1079
			memset(&pmb[rp], 0, sizeof(pmb[rp]));
1080 1081 1082 1083 1084 1085 1086 1087 1088
			rp--;
			lp++;
		}
	}

	/* Remember the number of running sets */
	return rp + 1;
}

1089 1090 1091
/*
 * If o->color_moved is COLOR_MOVED_PLAIN, this function does nothing.
 *
1092 1093
 * Otherwise, if the last block has fewer alphanumeric characters than
 * COLOR_MOVED_MIN_ALNUM_COUNT, unset DIFF_SYMBOL_MOVED_LINE on all lines in
1094 1095 1096 1097
 * that block.
 *
 * The last block consists of the (n - block_length)'th line up to but not
 * including the nth line.
1098
 *
1099 1100 1101
 * Returns 0 if the last block is empty or is unset by this function, non zero
 * otherwise.
 *
1102 1103
 * NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
 * Think of a way to unify them.
1104
 */
1105
static int adjust_last_block(struct diff_options *o, int n, int block_length)
1106
{
1107 1108
	int i, alnum_count = 0;
	if (o->color_moved == COLOR_MOVED_PLAIN)
1109
		return block_length;
1110 1111 1112 1113 1114 1115 1116
	for (i = 1; i < block_length + 1; i++) {
		const char *c = o->emitted_symbols->buf[n - i].line;
		for (; *c; c++) {
			if (!isalnum(*c))
				continue;
			alnum_count++;
			if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT)
1117
				return 1;
1118 1119
		}
	}
1120 1121
	for (i = 1; i < block_length + 1; i++)
		o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
1122
	return 0;
1123 1124
}

1125 1126 1127 1128 1129
/* Find blocks of moved code, delegate actual coloring decision to helper */
static void mark_color_as_moved(struct diff_options *o,
				struct hashmap *add_lines,
				struct hashmap *del_lines)
{
1130
	struct moved_block *pmb = NULL; /* potentially moved blocks */
1131
	int pmb_nr = 0, pmb_alloc = 0;
1132
	int n, flipped_block = 0, block_length = 0;
1133 1134 1135 1136 1137 1138 1139


	for (n = 0; n < o->emitted_symbols->nr; n++) {
		struct hashmap *hm = NULL;
		struct moved_entry *key;
		struct moved_entry *match = NULL;
		struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
1140
		enum diff_symbol last_symbol = 0;
1141 1142 1143 1144 1145

		switch (l->s) {
		case DIFF_SYMBOL_PLUS:
			hm = del_lines;
			key = prepare_entry(o, n);
1146
			match = hashmap_get(hm, key, NULL);
1147 1148 1149 1150 1151
			free(key);
			break;
		case DIFF_SYMBOL_MINUS:
			hm = add_lines;
			key = prepare_entry(o, n);
1152
			match = hashmap_get(hm, key, NULL);
1153 1154 1155
			free(key);
			break;
		default:
1156
			flipped_block = 0;
1157 1158 1159
		}

		if (!match) {
1160 1161
			int i;

1162
			adjust_last_block(o, n, block_length);
1163 1164
			for(i = 0; i < pmb_nr; i++)
				moved_block_clear(&pmb[i]);
1165 1166
			pmb_nr = 0;
			block_length = 0;
1167 1168
			flipped_block = 0;
			last_symbol = l->s;
1169 1170 1171
			continue;
		}

1172
		if (o->color_moved == COLOR_MOVED_PLAIN) {
1173
			last_symbol = l->s;
1174
			l->flags |= DIFF_SYMBOL_MOVED_LINE;
1175
			continue;
1176
		}
1177

1178 1179 1180 1181 1182
		if (o->color_moved_ws_handling &
		    COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
			pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n);
		else
			pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192

		pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);

		if (pmb_nr == 0) {
			/*
			 * The current line is the start of a new block.
			 * Setup the set of potential blocks.
			 */
			for (; match; match = hashmap_get_next(hm, match)) {
				ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
1193 1194
				if (o->color_moved_ws_handling &
				    COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
1195 1196 1197
					if (compute_ws_delta(l, match->es,
							     &pmb[pmb_nr].wsd))
						pmb[pmb_nr++].match = match;
1198
				} else {
1199
					pmb[pmb_nr].wsd = 0;
1200
					pmb[pmb_nr++].match = match;
1201
				}
1202 1203
			}

1204 1205 1206 1207 1208
			if (adjust_last_block(o, n, block_length) &&
			    pmb_nr && last_symbol != l->s)
				flipped_block = (flipped_block + 1) % 2;
			else
				flipped_block = 0;
1209 1210

			block_length = 0;
1211 1212
		}

1213 1214 1215 1216 1217 1218
		if (pmb_nr) {
			block_length++;
			l->flags |= DIFF_SYMBOL_MOVED_LINE;
			if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
				l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
		}
1219
		last_symbol = l->s;
1220
	}
1221
	adjust_last_block(o, n, block_length);
1222

1223 1224
	for(n = 0; n < pmb_nr; n++)
		moved_block_clear(&pmb[n]);
1225 1226
	free(pmb);
}
1227

1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288
#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
  (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
static void dim_moved_lines(struct diff_options *o)
{
	int n;
	for (n = 0; n < o->emitted_symbols->nr; n++) {
		struct emitted_diff_symbol *prev = (n != 0) ?
				&o->emitted_symbols->buf[n - 1] : NULL;
		struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
		struct emitted_diff_symbol *next =
				(n < o->emitted_symbols->nr - 1) ?
				&o->emitted_symbols->buf[n + 1] : NULL;

		/* Not a plus or minus line? */
		if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS)
			continue;

		/* Not a moved line? */
		if (!(l->flags & DIFF_SYMBOL_MOVED_LINE))
			continue;

		/*
		 * If prev or next are not a plus or minus line,
		 * pretend they don't exist
		 */
		if (prev && prev->s != DIFF_SYMBOL_PLUS &&
			    prev->s != DIFF_SYMBOL_MINUS)
			prev = NULL;
		if (next && next->s != DIFF_SYMBOL_PLUS &&
			    next->s != DIFF_SYMBOL_MINUS)
			next = NULL;

		/* Inside a block? */
		if ((prev &&
		    (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
		    (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) &&
		    (next &&
		    (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
		    (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) {
			l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
			continue;
		}

		/* Check if we are at an interesting bound: */
		if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) &&
		    (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
		       (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
			continue;
		if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) &&
		    (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
		       (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
			continue;

		/*
		 * The boundary to prev and next are not interesting,
		 * so this line is not interesting as a whole
		 */
		l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
	}
}

1289
static void emit_line_ws_markup(struct diff_options *o,
1290 1291
				const char *set_sign, const char *set,
				const char *reset,
1292
				int sign_index, const char *line, int len,
1293
				unsigned ws_rule, int blank_at_eof)
1294
{
1295
	const char *ws = NULL;
1296
	int sign = o->output_indicators[sign_index];
1297

1298 1299
	if (o->ws_error_highlight & ws_rule) {
		ws = diff_get_color_opt(o, DIFF_WHITESPACE);
1300 1301 1302 1303
		if (!*ws)
			ws = NULL;
	}

1304
	if (!ws && !set_sign)
1305
		emit_line_0(o, set, NULL, 0, reset, sign, line, len);
1306
	else if (!ws) {
1307
		emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len);
1308
	} else if (blank_at_eof)
1309
		/* Blank line at EOF - paint '+' as well */
1310
		emit_line_0(o, ws, NULL, 0, reset, sign, line, len);
1311 1312
	else {
		/* Emit just the prefix, then the rest. */
1313
		emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset,
1314
			    sign, "", 0);
1315 1316
		ws_check_emit(line, len, ws_rule,
			      o->file, set, reset, ws);
1317 1318 1319
	}
}

1320 1321
static void emit_diff_symbol_from_struct(struct diff_options *o,
					 struct emitted_diff_symbol *eds)
1322
{
1323
	static const char *nneof = " No newline at end of file\n";
1324
	const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
1325
	struct strbuf sb = STRBUF_INIT;
1326 1327 1328 1329 1330 1331

	enum diff_symbol s = eds->s;
	const char *line = eds->line;
	int len = eds->len;
	unsigned flags = eds->flags;

1332
	switch (s) {
1333 1334 1335 1336
	case DIFF_SYMBOL_NO_LF_EOF:
		context = diff_get_color_opt(o, DIFF_CONTEXT);
		reset = diff_get_color_opt(o, DIFF_RESET);
		putc('\n', o->file);
1337
		emit_line_0(o, context, NULL, 0, reset, '\\',
1338 1339
			    nneof, strlen(nneof));
		break;
1340 1341 1342
	case DIFF_SYMBOL_SUBMODULE_HEADER:
	case DIFF_SYMBOL_SUBMODULE_ERROR:
	case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH:
1343
	case DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES:
1344
	case DIFF_SYMBOL_SUMMARY:
1345
	case DIFF_SYMBOL_STATS_LINE:
1346
	case DIFF_SYMBOL_BINARY_DIFF_BODY:
1347 1348 1349
	case DIFF_SYMBOL_CONTEXT_FRAGINFO:
		emit_line(o, "", "", line, len);
		break;
1350
	case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
1351 1352 1353 1354 1355
	case DIFF_SYMBOL_CONTEXT_MARKER:
		context = diff_get_color_opt(o, DIFF_CONTEXT);
		reset = diff_get_color_opt(o, DIFF_RESET);
		emit_line(o, context, reset, line, len);
		break;
1356 1357 1358 1359 1360
	case DIFF_SYMBOL_SEPARATOR:
		fprintf(o->file, "%s%c",
			diff_line_prefix(o),
			o->line_termination);
		break;
1361 1362 1363
	case DIFF_SYMBOL_CONTEXT:
		set = diff_get_color_opt(o, DIFF_CONTEXT);
		reset = diff_get_color_opt(o, DIFF_RESET);
1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374
		set_sign = NULL;
		if (o->flags.dual_color_diffed_diffs) {
			char c = !len ? 0 : line[0];

			if (c == '+')
				set = diff_get_color_opt(o, DIFF_FILE_NEW);
			else if (c == '@')
				set = diff_get_color_opt(o, DIFF_FRAGINFO);
			else if (c == '-')
				set = diff_get_color_opt(o, DIFF_FILE_OLD);
		}
1375
		emit_line_ws_markup(o, set_sign, set, reset,
1376
				    OUTPUT_INDICATOR_CONTEXT, line, len,
1377 1378 1379
				    flags