From: Junio C Hamano <gitster@pobox.com>
Date: Tue, 19 Jan 2010 05:37:12 +0000 (-0800)
Subject: Merge branch 'maint-1.6.4' into maint-1.6.5
X-Git-Tag: v1.6.5.8~5
X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/03c6e97f809e5b89089a2c689d6a319e8d471455?hp=-c

Merge branch 'maint-1.6.4' into maint-1.6.5

* maint-1.6.4:
Fix mis-backport of t7002
base85: Make the code more obvious instead of explaining the non-obvious
base85: encode_85() does not use the decode table
base85 debug code: Fix length byte calculation
checkout -m: do not try to fall back to --merge from an unborn branch
branch: die explicitly why when calling "git branch [-a|-r] branchname".
textconv: stop leaking file descriptors
commit: --cleanup is a message option
git count-objects: handle packs bigger than 4G
t7102: make the test fail if one of its check fails
---

03c6e97f809e5b89089a2c689d6a319e8d471455
diff --combined builtin-branch.c
index c77f632886,316a8336c6..0c84f9f9eb
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@@ -65,7 -65,7 +65,7 @@@ static int parse_branch_color_slot(cons
  		return BRANCH_COLOR_LOCAL;
  	if (!strcasecmp(var+ofs, "current"))
  		return BRANCH_COLOR_CURRENT;
 -	die("bad config variable '%s'", var);
 +	return -1;
  }
  
  static int git_branch_config(const char *var, const char *value, void *cb)
@@@ -76,8 -76,6 +76,8 @@@
  	}
  	if (!prefixcmp(var, "color.branch.")) {
  		int slot = parse_branch_color_slot(var, 13);
 +		if (slot < 0)
 +			return 0;
  		if (!value)
  			return config_error_nonbool(var);
  		color_parse(value, var, branch_colors[slot]);
@@@ -588,7 -586,7 +588,7 @@@ int cmd_branch(int argc, const char **a
  		OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
  		OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
  		OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
 -		OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"),
 +		OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
  		{
  			OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
  			"commit", "print only not merged branches",
@@@ -637,10 -635,12 +637,12 @@@
  		rename_branch(head, argv[0], rename > 1);
  	else if (rename && (argc == 2))
  		rename_branch(argv[0], argv[1], rename > 1);
- 	else if (argc <= 2)
+ 	else if (argc <= 2) {
+ 		if (kinds != REF_LOCAL_BRANCH)
+ 			die("-a and -r options to 'git branch' do not make sense with a branch name");
  		create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
  			      force_create, reflog, track);
- 	else
+ 	} else
  		usage_with_options(builtin_branch_usage, options);
  
  	return 0;
diff --combined builtin-checkout.c
index d050c3789f,e2dd0cd0c0..f2786fe439
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@@ -396,7 -396,7 +396,7 @@@ static int merge_working_tree(struct ch
  		topts.initial_checkout = is_cache_unborn();
  		topts.update = 1;
  		topts.merge = 1;
- 		topts.gently = opts->merge;
+ 		topts.gently = opts->merge && old->commit;
  		topts.verbose_update = !opts->quiet;
  		topts.fn = twoway_merge;
  		topts.dir = xcalloc(1, sizeof(*topts.dir));
@@@ -421,7 -421,13 +421,13 @@@
  			struct merge_options o;
  			if (!opts->merge)
  				return 1;
- 			parse_commit(old->commit);
+ 
+ 			/*
+ 			 * Without old->commit, the below is the same as
+ 			 * the two-tree unpack we already tried and failed.
+ 			 */
+ 			if (!old->commit)
+ 				return 1;
  
  			/* Do more real merge */
  
@@@ -566,13 -572,6 +572,13 @@@ static int git_checkout_config(const ch
  	return git_xmerge_config(var, value, cb);
  }
  
 +static int interactive_checkout(const char *revision, const char **pathspec,
 +				struct checkout_opts *opts)
 +{
 +	return run_add_interactive(revision, "--patch=checkout", pathspec);
 +}
 +
 +
  int cmd_checkout(int argc, const char **argv, const char *prefix)
  {
  	struct checkout_opts opts;
@@@ -581,7 -580,6 +587,7 @@@
  	struct branch_info new;
  	struct tree *source_tree = NULL;
  	char *conflict_style = NULL;
 +	int patch_mode = 0;
  	struct option options[] = {
  		OPT__QUIET(&opts.quiet),
  		OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
@@@ -592,11 -590,10 +598,11 @@@
  			    2),
  		OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
  			    3),
 -		OPT_BOOLEAN('f', NULL, &opts.force, "force"),
 +		OPT_BOOLEAN('f', "force", &opts.force, "force"),
  		OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
  		OPT_STRING(0, "conflict", &conflict_style, "style",
  			   "conflict style (merge or diff3)"),
 +		OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
  		OPT_END(),
  	};
  	int has_dash_dash;
@@@ -611,10 -608,6 +617,10 @@@
  	argc = parse_options(argc, argv, prefix, options, checkout_usage,
  			     PARSE_OPT_KEEP_DASHDASH);
  
 +	if (patch_mode && (opts.track > 0 || opts.new_branch
 +			   || opts.new_branch_log || opts.merge || opts.force))
 +		die ("--patch is incompatible with all other options");
 +
  	/* --track without -b should DWIM */
  	if (0 < opts.track && !opts.new_branch) {
  		const char *argv0 = argv[0];
@@@ -721,9 -714,6 +727,9 @@@ no_reference
  		if (!pathspec)
  			die("invalid path specification");
  
 +		if (patch_mode)
 +			return interactive_checkout(new.name, pathspec, &opts);
 +
  		/* Checkout paths */
  		if (opts.new_branch) {
  			if (argc == 1) {
@@@ -739,9 -729,6 +745,9 @@@
  		return checkout_paths(source_tree, pathspec, &opts);
  	}
  
 +	if (patch_mode)
 +		return interactive_checkout(new.name, NULL, &opts);
 +
  	if (opts.new_branch) {
  		struct strbuf buf = STRBUF_INIT;
  		if (strbuf_check_branch_ref(&buf, opts.new_branch))
diff --combined builtin-commit.c
index 7d3c6a86f7,26c3cc4e57..c2ab85e1aa
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@@ -51,7 -51,7 +51,7 @@@ static const char *template_file
  static char *edit_message, *use_message;
  static char *author_name, *author_email, *author_date;
  static int all, edit_flag, also, interactive, only, amend, signoff;
 -static int quiet, verbose, no_verify, allow_empty;
 +static int quiet, verbose, no_verify, allow_empty, dry_run;
  static char *untracked_files_arg;
  /*
   * The default commit message cleanup mode will remove the lines
@@@ -86,8 -86,8 +86,8 @@@ static int opt_parse_m(const struct opt
  static struct option builtin_commit_options[] = {
  	OPT__QUIET(&quiet),
  	OPT__VERBOSE(&verbose),
- 	OPT_GROUP("Commit message options"),
  
+ 	OPT_GROUP("Commit message options"),
  	OPT_FILENAME('F', "file", &logfile, "read log from file"),
  	OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
  	OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
@@@ -96,6 -96,8 +96,8 @@@
  	OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
  	OPT_FILENAME('t', "template", &template_file, "use specified template file"),
  	OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+ 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ 	/* end commit message options */
  
  	OPT_GROUP("Commit contents options"),
  	OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
@@@ -103,11 -105,10 +105,11 @@@
  	OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
  	OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
  	OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
 +	OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
  	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
  	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
  	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
- 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ 	/* end commit contents options */
  
  	OPT_END()
  };
@@@ -218,15 -219,12 +220,15 @@@ static void create_base_index(void
  		exit(128); /* We've already reported the error, finish dying */
  }
  
 -static char *prepare_index(int argc, const char **argv, const char *prefix)
 +static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
  {
  	int fd;
  	struct string_list partial;
  	const char **pathspec = NULL;
 +	int refresh_flags = REFRESH_QUIET;
  
 +	if (is_status)
 +		refresh_flags |= REFRESH_UNMERGED;
  	if (interactive) {
  		if (interactive_add(argc, argv, prefix) != 0)
  			die("interactive add failed");
@@@ -257,7 -255,7 +259,7 @@@
  	if (all || (also && pathspec && *pathspec)) {
  		int fd = hold_locked_index(&index_lock, 1);
  		add_files_to_cache(also ? prefix : NULL, pathspec, 0);
 -		refresh_cache(REFRESH_QUIET);
 +		refresh_cache(refresh_flags);
  		if (write_cache(fd, active_cache, active_nr) ||
  		    close_lock_file(&index_lock))
  			die("unable to write new_index file");
@@@ -276,7 -274,7 +278,7 @@@
  	 */
  	if (!pathspec || !*pathspec) {
  		fd = hold_locked_index(&index_lock, 1);
 -		refresh_cache(REFRESH_QUIET);
 +		refresh_cache(refresh_flags);
  		if (write_cache(fd, active_cache, active_nr) ||
  		    commit_locked_index(&index_lock))
  			die("unable to write new_index file");
@@@ -343,24 -341,27 +345,24 @@@
  	return false_lock.filename;
  }
  
 -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn)
 +static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
 +		      struct wt_status *s)
  {
 -	struct wt_status s;
 -
 -	wt_status_prepare(&s);
 -	if (wt_status_relative_paths)
 -		s.prefix = prefix;
 +	if (s->relative_paths)
 +		s->prefix = prefix;
  
  	if (amend) {
 -		s.amend = 1;
 -		s.reference = "HEAD^1";
 +		s->amend = 1;
 +		s->reference = "HEAD^1";
  	}
 -	s.verbose = verbose;
 -	s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES);
 -	s.index_file = index_file;
 -	s.fp = fp;
 -	s.nowarn = nowarn;
 +	s->verbose = verbose;
 +	s->index_file = index_file;
 +	s->fp = fp;
 +	s->nowarn = nowarn;
  
 -	wt_status_print(&s);
 +	wt_status_print(s);
  
 -	return s.commitable;
 +	return s->commitable;
  }
  
  static int is_a_merge(const unsigned char *sha1)
@@@ -414,8 -415,7 +416,8 @@@ static void determine_author_info(void
  	author_date = date;
  }
  
 -static int prepare_to_commit(const char *index_file, const char *prefix)
 +static int prepare_to_commit(const char *index_file, const char *prefix,
 +			     struct wt_status *s)
  {
  	struct stat statbuf;
  	int commitable, saved_color_setting;
@@@ -557,10 -557,10 +559,10 @@@
  		if (ident_shown)
  			fprintf(fp, "#\n");
  
 -		saved_color_setting = wt_status_use_color;
 -		wt_status_use_color = 0;
 -		commitable = run_status(fp, index_file, prefix, 1);
 -		wt_status_use_color = saved_color_setting;
 +		saved_color_setting = s->use_color;
 +		s->use_color = 0;
 +		commitable = run_status(fp, index_file, prefix, 1, s);
 +		s->use_color = saved_color_setting;
  	} else {
  		unsigned char sha1[20];
  		const char *parent = "HEAD";
@@@ -581,7 -581,7 +583,7 @@@
  
  	if (!commitable && !in_merge && !allow_empty &&
  	    !(amend && is_a_merge(head_sha1))) {
 -		run_status(stdout, index_file, prefix, 0);
 +		run_status(stdout, index_file, prefix, 0, s);
  		return 0;
  	}
  
@@@ -693,8 -693,7 +695,8 @@@ static const char *find_author_by_nickn
  
  static int parse_and_validate_options(int argc, const char *argv[],
  				      const char * const usage[],
 -				      const char *prefix)
 +				      const char *prefix,
 +				      struct wt_status *s)
  {
  	int f = 0;
  
@@@ -797,11 -796,11 +799,11 @@@
  	if (!untracked_files_arg)
  		; /* default already initialized */
  	else if (!strcmp(untracked_files_arg, "no"))
 -		show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +		s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
  	else if (!strcmp(untracked_files_arg, "normal"))
 -		show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +		s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
  	else if (!strcmp(untracked_files_arg, "all"))
 -		show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +		s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
  	else
  		die("Invalid untracked files mode '%s'", untracked_files_arg);
  
@@@ -813,95 -812,28 +815,95 @@@
  	return argc;
  }
  
 -int cmd_status(int argc, const char **argv, const char *prefix)
 +static int dry_run_commit(int argc, const char **argv, const char *prefix,
 +			  struct wt_status *s)
  {
 -	const char *index_file;
  	int commitable;
 +	const char *index_file;
  
 -	git_config(git_status_config, NULL);
 +	index_file = prepare_index(argc, argv, prefix, 1);
 +	commitable = run_status(stdout, index_file, prefix, 0, s);
 +	rollback_index_files();
  
 -	if (wt_status_use_color == -1)
 -		wt_status_use_color = git_use_color_default;
 +	return commitable ? 0 : 1;
 +}
  
 -	if (diff_use_color_default == -1)
 -		diff_use_color_default = git_use_color_default;
 +static int parse_status_slot(const char *var, int offset)
 +{
 +	if (!strcasecmp(var+offset, "header"))
 +		return WT_STATUS_HEADER;
 +	if (!strcasecmp(var+offset, "updated")
 +		|| !strcasecmp(var+offset, "added"))
 +		return WT_STATUS_UPDATED;
 +	if (!strcasecmp(var+offset, "changed"))
 +		return WT_STATUS_CHANGED;
 +	if (!strcasecmp(var+offset, "untracked"))
 +		return WT_STATUS_UNTRACKED;
 +	if (!strcasecmp(var+offset, "nobranch"))
 +		return WT_STATUS_NOBRANCH;
 +	if (!strcasecmp(var+offset, "unmerged"))
 +		return WT_STATUS_UNMERGED;
 +	return -1;
 +}
  
 -	argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
 +static int git_status_config(const char *k, const char *v, void *cb)
 +{
 +	struct wt_status *s = cb;
  
 -	index_file = prepare_index(argc, argv, prefix);
 +	if (!strcmp(k, "status.submodulesummary")) {
 +		int is_bool;
 +		s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
 +		if (is_bool && s->submodule_summary)
 +			s->submodule_summary = -1;
 +		return 0;
 +	}
 +	if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 +		s->use_color = git_config_colorbool(k, v, -1);
 +		return 0;
 +	}
 +	if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
 +		int slot = parse_status_slot(k, 13);
 +		if (slot < 0)
 +			return 0;
 +		if (!v)
 +			return config_error_nonbool(k);
 +		color_parse(v, k, s->color_palette[slot]);
 +		return 0;
 +	}
 +	if (!strcmp(k, "status.relativepaths")) {
 +		s->relative_paths = git_config_bool(k, v);
 +		return 0;
 +	}
 +	if (!strcmp(k, "status.showuntrackedfiles")) {
 +		if (!v)
 +			return config_error_nonbool(k);
 +		else if (!strcmp(v, "no"))
 +			s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +		else if (!strcmp(v, "normal"))
 +			s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +		else if (!strcmp(v, "all"))
 +			s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +		else
 +			return error("Invalid untracked files mode '%s'", v);
 +		return 0;
 +	}
 +	return git_diff_ui_config(k, v, NULL);
 +}
  
 -	commitable = run_status(stdout, index_file, prefix, 0);
 +int cmd_status(int argc, const char **argv, const char *prefix)
 +{
 +	struct wt_status s;
  
 -	rollback_index_files();
 +	wt_status_prepare(&s);
 +	git_config(git_status_config, &s);
 +	if (s.use_color == -1)
 +		s.use_color = git_use_color_default;
 +	if (diff_use_color_default == -1)
 +		diff_use_color_default = git_use_color_default;
  
 -	return commitable ? 0 : 1;
 +	argc = parse_and_validate_options(argc, argv, builtin_status_usage,
 +					  prefix, &s);
 +	return dry_run_commit(argc, argv, prefix, &s);
  }
  
  static void print_summary(const char *prefix, const unsigned char *sha1)
@@@ -953,12 -885,10 +955,12 @@@
  
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
 +	struct wt_status *s = cb;
 +
  	if (!strcmp(k, "commit.template"))
 -		return git_config_string(&template_file, k, v);
 +		return git_config_pathname(&template_file, k, v);
  
 -	return git_status_config(k, v, cb);
 +	return git_status_config(k, v, s);
  }
  
  int cmd_commit(int argc, const char **argv, const char *prefix)
@@@ -971,26 -901,19 +973,26 @@@
  	struct commit_list *parents = NULL, **pptr = &parents;
  	struct stat statbuf;
  	int allow_fast_forward = 1;
 +	struct wt_status s;
  
 -	git_config(git_commit_config, NULL);
 -
 -	if (wt_status_use_color == -1)
 -		wt_status_use_color = git_use_color_default;
 +	wt_status_prepare(&s);
 +	git_config(git_commit_config, &s);
  
 -	argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
 +	if (s.use_color == -1)
 +		s.use_color = git_use_color_default;
  
 -	index_file = prepare_index(argc, argv, prefix);
 +	argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
 +					  prefix, &s);
 +	if (dry_run) {
 +		if (diff_use_color_default == -1)
 +			diff_use_color_default = git_use_color_default;
 +		return dry_run_commit(argc, argv, prefix, &s);
 +	}
 +	index_file = prepare_index(argc, argv, prefix, 0);
  
  	/* Set up everything for writing the commit object.  This includes
  	   running hooks, writing the trees, and interacting with the user.  */
 -	if (!prepare_to_commit(index_file, prefix)) {
 +	if (!prepare_to_commit(index_file, prefix, &s)) {
  		rollback_index_files();
  		return 1;
  	}
diff --combined diff.c
index 72f25b5284,6f3bd85979..17a2b4df29
--- a/diff.c
+++ b/diff.c
@@@ -59,7 -59,7 +59,7 @@@ static int parse_diff_color_slot(const 
  		return DIFF_COMMIT;
  	if (!strcasecmp(var+ofs, "whitespace"))
  		return DIFF_WHITESPACE;
 -	die("bad config variable '%s'", var);
 +	return -1;
  }
  
  static int git_config_rename(const char *var, const char *value)
@@@ -118,8 -118,6 +118,8 @@@ int git_diff_basic_config(const char *v
  
  	if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
  		int slot = parse_diff_color_slot(var, 11);
 +		if (slot < 0)
 +			return 0;
  		if (!value)
  			return config_error_nonbool(var);
  		color_parse(value, var, diff_colors[slot]);
@@@ -176,175 -174,6 +176,175 @@@ static struct diff_tempfile 
  	char tmp_path[PATH_MAX];
  } diff_temp[2];
  
 +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 +
 +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;
 +	sane_truncate_fn truncate;
 +	const char **label_path;
 +	struct diff_words_data *diff_words;
 +	int *found_changesp;
 +	FILE *file;
 +};
 +
 +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;
 +}
 +
 +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 +{
 +	if (!DIFF_FILE_VALID(one)) {
 +		mf->ptr = (char *)""; /* does not matter */
 +		mf->size = 0;
 +		return 0;
 +	}
 +	else if (diff_populate_filespec(one, 0))
 +		return -1;
 +
 +	mf->ptr = one->data;
 +	mf->size = one->size;
 +	return 0;
 +}
 +
 +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;
 +}
 +
 +static void emit_line_0(FILE *file, const char *set, const char *reset,
 +			int first, const char *line, int len)
 +{
 +	int has_trailing_newline, has_trailing_carriage_return;
 +	int nofirst;
 +
 +	if (len == 0) {
 +		has_trailing_newline = (first == '\n');
 +		has_trailing_carriage_return = (!has_trailing_newline &&
 +						(first == '\r'));
 +		nofirst = has_trailing_newline || has_trailing_carriage_return;
 +	} else {
 +		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--;
 +		nofirst = 0;
 +	}
 +
 +	fputs(set, file);
 +
 +	if (!nofirst)
 +		fputc(first, file);
 +	fwrite(line, len, 1, file);
 +	fputs(reset, file);
 +	if (has_trailing_carriage_return)
 +		fputc('\r', file);
 +	if (has_trailing_newline)
 +		fputc('\n', file);
 +}
 +
 +static void emit_line(FILE *file, const char *set, const char *reset,
 +		      const char *line, int len)
 +{
 +	emit_line_0(file, set, reset, line[0], line+1, len-1);
 +}
 +
 +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
 +{
 +	if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
 +	      ecbdata->blank_at_eof_in_preimage &&
 +	      ecbdata->blank_at_eof_in_postimage &&
 +	      ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
 +	      ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
 +		return 0;
 +	return ws_blank_line(line, len, ecbdata->ws_rule);
 +}
 +
 +static void emit_add_line(const char *reset,
 +			  struct emit_callback *ecbdata,
 +			  const char *line, int len)
 +{
 +	const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 +	const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 +
 +	if (!*ws)
 +		emit_line_0(ecbdata->file, set, reset, '+', line, len);
 +	else if (new_blank_line_at_eof(ecbdata, line, len))
 +		/* Blank line at EOF - paint '+' as well */
 +		emit_line_0(ecbdata->file, ws, reset, '+', line, len);
 +	else {
 +		/* Emit just the prefix, then the rest. */
 +		emit_line_0(ecbdata->file, set, reset, '+', "", 0);
 +		ws_check_emit(line, len, ecbdata->ws_rule,
 +			      ecbdata->file, set, reset, ws);
 +	}
 +}
 +
  static struct diff_tempfile *claim_diff_tempfile(void) {
  	int i;
  	for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@@ -372,6 -201,29 +372,6 @@@ static void remove_tempfile_on_signal(i
  	raise(signo);
  }
  
 -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;
 -}
 -
  static void print_line_count(FILE *file, int count)
  {
  	switch (count) {
@@@ -387,36 -239,26 +387,36 @@@
  	}
  }
  
 -static void copy_file_with_prefix(FILE *file,
 -				  int prefix, const char *data, int size,
 -				  const char *set, const char *reset)
 +static void emit_rewrite_lines(struct emit_callback *ecb,
 +			       int prefix, const char *data, int size)
  {
 -	int ch, nl_just_seen = 1;
 -	while (0 < size--) {
 -		ch = *data++;
 -		if (nl_just_seen) {
 -			fputs(set, file);
 -			putc(prefix, file);
 +	const char *endp = NULL;
 +	static const char *nneof = " No newline at end of file\n";
 +	const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
 +	const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 +
 +	while (0 < size) {
 +		int len;
 +
 +		endp = memchr(data, '\n', size);
 +		len = endp ? (endp - data + 1) : size;
 +		if (prefix != '+') {
 +			ecb->lno_in_preimage++;
 +			emit_line_0(ecb->file, old, reset, '-',
 +				    data, len);
 +		} else {
 +			ecb->lno_in_postimage++;
 +			emit_add_line(reset, ecb, data, len);
  		}
 -		if (ch == '\n') {
 -			nl_just_seen = 1;
 -			fputs(reset, file);
 -		} else
 -			nl_just_seen = 0;
 -		putc(ch, file);
 +		size -= len;
 +		data += len;
 +	}
 +	if (!endp) {
 +		const char *plain = diff_get_color(ecb->color_diff,
 +						   DIFF_PLAIN);
 +		emit_line_0(ecb->file, plain, reset, '\\',
 +			    nneof, strlen(nneof));
  	}
 -	if (!nl_just_seen)
 -		fprintf(file, "%s\n\\ No newline at end of file\n", reset);
  }
  
  static void emit_rewrite_diff(const char *name_a,
@@@ -432,12 -274,13 +432,12 @@@
  	const char *name_a_tab, *name_b_tab;
  	const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
  	const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
 -	const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
 -	const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
  	const char *reset = diff_get_color(color_diff, DIFF_RESET);
  	static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
  	const char *a_prefix, *b_prefix;
  	const char *data_one, *data_two;
  	size_t size_one, size_two;
 +	struct emit_callback ecbdata;
  
  	if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
  		a_prefix = o->b_prefix;
@@@ -478,22 -321,6 +478,22 @@@
  		size_two = two->size;
  	}
  
 +	memset(&ecbdata, 0, sizeof(ecbdata));
 +	ecbdata.color_diff = color_diff;
 +	ecbdata.found_changesp = &o->found_changes;
 +	ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +	ecbdata.file = o->file;
 +	if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
 +		mmfile_t mf1, mf2;
 +		mf1.ptr = (char *)data_one;
 +		mf2.ptr = (char *)data_two;
 +		mf1.size = size_one;
 +		mf2.size = size_two;
 +		check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +	}
 +	ecbdata.lno_in_preimage = 1;
 +	ecbdata.lno_in_postimage = 1;
 +
  	lc_a = count_lines(data_one, size_one);
  	lc_b = count_lines(data_two, size_two);
  	fprintf(o->file,
@@@ -505,9 -332,24 +505,9 @@@
  	print_line_count(o->file, lc_b);
  	fprintf(o->file, " @@%s\n", reset);
  	if (lc_a)
 -		copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset);
 +		emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
  	if (lc_b)
 -		copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset);
 -}
 -
 -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 -{
 -	if (!DIFF_FILE_VALID(one)) {
 -		mf->ptr = (char *)""; /* does not matter */
 -		mf->size = 0;
 -		return 0;
 -	}
 -	else if (diff_populate_filespec(one, 0))
 -		return -1;
 -
 -	mf->ptr = one->data;
 -	mf->size = one->size;
 -	return 0;
 +		emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
  }
  
  struct diff_words_buffer {
@@@ -687,18 -529,26 +687,18 @@@ static void diff_words_show(struct diff
  	diff_words->minus.text.size = diff_words->plus.text.size = 0;
  }
  
 -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 -
 -struct emit_callback {
 -	int nparents, color_diff;
 -	unsigned ws_rule;
 -	sane_truncate_fn truncate;
 -	const char **label_path;
 -	struct diff_words_data *diff_words;
 -	int *found_changesp;
 -	FILE *file;
 -};
 +/* In "color-words" mode, show word-diff of words accumulated in the buffer */
 +static void diff_words_flush(struct emit_callback *ecbdata)
 +{
 +	if (ecbdata->diff_words->minus.text.size ||
 +	    ecbdata->diff_words->plus.text.size)
 +		diff_words_show(ecbdata->diff_words);
 +}
  
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
  	if (ecbdata->diff_words) {
 -		/* flush buffers */
 -		if (ecbdata->diff_words->minus.text.size ||
 -				ecbdata->diff_words->plus.text.size)
 -			diff_words_show(ecbdata->diff_words);
 -
 +		diff_words_flush(ecbdata);
  		free (ecbdata->diff_words->minus.text.ptr);
  		free (ecbdata->diff_words->minus.orig);
  		free (ecbdata->diff_words->plus.text.ptr);
@@@ -716,6 -566,42 +716,6 @@@ const char *diff_get_color(int diff_use
  	return "";
  }
  
 -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
 -{
 -	int has_trailing_newline, has_trailing_carriage_return;
 -
 -	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--;
 -
 -	fputs(set, file);
 -	fwrite(line, len, 1, file);
 -	fputs(reset, file);
 -	if (has_trailing_carriage_return)
 -		fputc('\r', file);
 -	if (has_trailing_newline)
 -		fputc('\n', file);
 -}
 -
 -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
 -{
 -	const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 -	const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 -
 -	if (!*ws)
 -		emit_line(ecbdata->file, set, reset, line, len);
 -	else {
 -		/* Emit just the prefix, then the rest. */
 -		emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
 -		ws_check_emit(line + ecbdata->nparents,
 -			      len - ecbdata->nparents, ecbdata->ws_rule,
 -			      ecbdata->file, set, reset, ws);
 -	}
 -}
 -
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
  	const char *cp;
@@@ -734,23 -620,10 +734,23 @@@
  	return allot - l;
  }
  
 +static void find_lno(const char *line, struct emit_callback *ecbdata)
 +{
 +	const char *p;
 +	ecbdata->lno_in_preimage = 0;
 +	ecbdata->lno_in_postimage = 0;
 +	p = strchr(line, '-');
 +	if (!p)
 +		return; /* cannot happen */
 +	ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10);
 +	p = strchr(p, '+');
 +	if (!p)
 +		return; /* cannot happen */
 +	ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
 +}
 +
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
 -	int i;
 -	int color;
  	struct emit_callback *ecbdata = priv;
  	const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
  	const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
@@@ -777,11 -650,14 +777,11 @@@
  		len = 1;
  	}
  
 -	/* This is not really necessary for now because
 -	 * this codepath only deals with two-way diffs.
 -	 */
 -	for (i = 0; i < len && line[i] == '@'; i++)
 -		;
 -	if (2 <= i && i < len && line[i] == ' ') {
 -		ecbdata->nparents = i - 1;
 +	if (line[0] == '@') {
 +		if (ecbdata->diff_words)
 +			diff_words_flush(ecbdata);
  		len = sane_truncate_line(ecbdata, line, len);
 +		find_lno(line, ecbdata);
  		emit_line(ecbdata->file,
  			  diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
  			  reset, line, len);
@@@ -790,11 -666,15 +790,11 @@@
  		return;
  	}
  
 -	if (len < ecbdata->nparents) {
 +	if (len < 1) {
  		emit_line(ecbdata->file, reset, reset, line, len);
  		return;
  	}
  
 -	color = DIFF_PLAIN;
 -	if (ecbdata->diff_words && ecbdata->nparents != 1)
 -		/* fall back to normal diff */
 -		free_diff_words_data(ecbdata);
  	if (ecbdata->diff_words) {
  		if (line[0] == '-') {
  			diff_words_append(line, len,
@@@ -805,25 -685,28 +805,25 @@@
  					  &ecbdata->diff_words->plus);
  			return;
  		}
 -		if (ecbdata->diff_words->minus.text.size ||
 -		    ecbdata->diff_words->plus.text.size)
 -			diff_words_show(ecbdata->diff_words);
 +		diff_words_flush(ecbdata);
  		line++;
  		len--;
  		emit_line(ecbdata->file, plain, reset, line, len);
  		return;
  	}
 -	for (i = 0; i < ecbdata->nparents && len; i++) {
 -		if (line[i] == '-')
 -			color = DIFF_FILE_OLD;
 -		else if (line[i] == '+')
 -			color = DIFF_FILE_NEW;
 -	}
  
 -	if (color != DIFF_FILE_NEW) {
 -		emit_line(ecbdata->file,
 -			  diff_get_color(ecbdata->color_diff, color),
 -			  reset, line, len);
 -		return;
 +	if (line[0] != '+') {
 +		const char *color =
 +			diff_get_color(ecbdata->color_diff,
 +				       line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
 +		ecbdata->lno_in_preimage++;
 +		if (line[0] == ' ')
 +			ecbdata->lno_in_postimage++;
 +		emit_line(ecbdata->file, color, reset, line, len);
 +	} else {
 +		ecbdata->lno_in_postimage++;
 +		emit_add_line(reset, ecbdata, line + 1, len - 1);
  	}
 -	emit_add_line(reset, ecbdata, line, len);
  }
  
  static char *pprint_rename(const char *a, const char *b)
@@@ -1328,6 -1211,7 +1328,6 @@@ struct checkdiff_t 
  	struct diff_options *o;
  	unsigned ws_rule;
  	unsigned status;
 -	int trailing_blanks_start;
  };
  
  static int is_conflict_marker(const char *line, unsigned long len)
@@@ -1371,6 -1255,10 +1371,6 @@@ static void checkdiff_consume(void *pri
  	if (line[0] == '+') {
  		unsigned bad;
  		data->lineno++;
 -		if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
 -			data->trailing_blanks_start = 0;
 -		else if (!data->trailing_blanks_start)
 -			data->trailing_blanks_start = data->lineno;
  		if (is_conflict_marker(line + 1, len - 1)) {
  			data->status |= 1;
  			fprintf(data->o->file,
@@@ -1390,12 -1278,14 +1390,12 @@@
  			      data->o->file, set, reset, ws);
  	} else if (line[0] == ' ') {
  		data->lineno++;
 -		data->trailing_blanks_start = 0;
  	} else if (line[0] == '@') {
  		char *plus = strchr(line, '+');
  		if (plus)
  			data->lineno = strtol(plus, NULL, 10) - 1;
  		else
  			die("invalid diff");
 -		data->trailing_blanks_start = 0;
  	}
  }
  
@@@ -1672,8 -1562,6 +1672,8 @@@ static void builtin_diff(const char *na
  		ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
  		ecbdata.found_changesp = &o->found_changes;
  		ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +		if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 +			check_blank_at_eof(&mf1, &mf2, &ecbdata);
  		ecbdata.file = o->file;
  		xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
  		xecfg.ctxlen = o->context;
@@@ -1816,22 -1704,11 +1816,22 @@@ static void builtin_checkdiff(const cha
  		xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
  			      &xpp, &xecfg, &ecb);
  
 -		if ((data.ws_rule & WS_TRAILING_SPACE) &&
 -		    data.trailing_blanks_start) {
 -			fprintf(o->file, "%s:%d: ends with blank lines.\n",
 -				data.filename, data.trailing_blanks_start);
 -			data.status = 1; /* report errors */
 +		if (data.ws_rule & WS_BLANK_AT_EOF) {
 +			struct emit_callback ecbdata;
 +			int blank_at_eof;
 +
 +			ecbdata.ws_rule = data.ws_rule;
 +			check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +			blank_at_eof = ecbdata.blank_at_eof_in_preimage;
 +
 +			if (blank_at_eof) {
 +				static char *err;
 +				if (!err)
 +					err = whitespace_error_string(WS_BLANK_AT_EOF);
 +				fprintf(o->file, "%s:%d: %s.\n",
 +					data.filename, blank_at_eof, err);
 +				data.status = 1; /* report errors */
 +			}
  		}
  	}
   free_and_return:
@@@ -2814,7 -2691,7 +2814,7 @@@ static int parse_num(const char **cp_p
  	num = 0;
  	scale = 1;
  	dot = 0;
 -	for(;;) {
 +	for (;;) {
  		ch = *cp;
  		if ( !dot && ch == '.' ) {
  			scale = 1;
@@@ -3720,11 -3597,13 +3720,13 @@@ static char *run_textconv(const char *p
  	if (start_command(&child) != 0 ||
  	    strbuf_read(&buf, child.out, 0) < 0 ||
  	    finish_command(&child) != 0) {
+ 		close(child.out);
  		strbuf_release(&buf);
  		remove_tempfile();
  		error("error running textconv command '%s'", pgm);
  		return NULL;
  	}
+ 	close(child.out);
  	remove_tempfile();
  
  	return strbuf_detach(&buf, outsize);
diff --combined t/t7102-reset.sh
index e85ff02c3e,5f3916bf4f..b8cf2603a1
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@@ -139,19 -139,19 +139,19 @@@ test_expect_success 
  test_expect_success \
  	'resetting to HEAD with no changes should succeed and do nothing' '
  	git reset --hard &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset --hard HEAD &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset --soft &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset --soft HEAD &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset --mixed &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset --mixed HEAD &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset &&
- 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+ 		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
  	git reset HEAD &&
  		check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
  '
@@@ -419,8 -419,7 +419,8 @@@ test_expect_success 'resetting an unmod
  '
  
  cat > expect << EOF
 -file2: locally modified
 +Unstaged changes after reset:
 +M	file2
  EOF
  
  test_expect_success '--mixed refreshes the index' '