builtin/checkout: fix git checkout -p HEAD... bug

Running `git checkout -p` with a merge-base rev results in an error:

	$ git checkout -p HEAD...
	usage: git diff-index [-m] [--cached] [<common-diff-options>] <tree-ish> [<path>...]
	common diff options:
	  -z            output diff-raw with lines terminated with NUL.
	  -p            output patch format.
	  -u            synonym for -p.
	  --patch-with-raw
			output both a patch and the diff-raw format.
	  --stat        show diffstat instead of patch.
	  --numstat     show numeric diffstat instead of patch.
	  --patch-with-stat
			output a patch and prepend its diffstat.
	  --name-only   show only names of changed files.
	  --name-status show names and status of changed files.
	  --full-index  show full object name on index lines.
	  --abbrev=<n>  abbreviate object names in diff-tree header and diff-raw.
	  -R            swap input file pairs.
	  -B            detect complete rewrites.
	  -M            detect renames.
	  -C            detect copies.
	  --find-copies-harder
			try unchanged files as candidate for copy detection.
	  -l<n>         limit rename attempts up to <n> paths.
	  -O<file>      reorder diffs according to the <file>.
	  -S<string>    find filepair whose only one side contains the string.
	  --pickaxe-all
			show all files diff when -S is used and hit is found.
	  -a  --text    treat all files as text.

	Cannot close git diff-index --cached --numstat --summary HEAD... -- () at <redacted>/libexec/git-core/git-add--interactive line 183.

This happens because checkout passes the literal argument (in the
example, `HEAD...`) to diff-index which does not recognise merge-base
revs.

Fix this by using the hex of the found commit instead of the given name.
Note that "HEAD" is handled specially in run_add_interactive() so it's
explicitly not changed.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Denton Liu 2020-10-07 00:56:15 -07:00 committed by Junio C Hamano
parent 47ae905ffb
commit 5602b500c3
3 changed files with 29 additions and 1 deletions

View file

@ -470,6 +470,19 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->patch_mode) {
const char *patch_mode;
const char *rev = new_branch_info->name;
char rev_oid[GIT_MAX_HEXSZ + 1];
/*
* Since rev can be in the form of `<a>...<b>` (which is not
* recognized by diff-index), we will always replace the name
* with the hex of the commit (whether it's in `...` form or
* not) for the run_add_interactive() machinery to work
* properly. However, there is special logic for the HEAD case
* so we mustn't replace that.
*/
if (rev && strcmp(rev, "HEAD"))
rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid);
if (opts->checkout_index && opts->checkout_worktree)
patch_mode = "--patch=checkout";
@ -480,7 +493,7 @@ static int checkout_paths(const struct checkout_opts *opts,
else
BUG("either flag must have been set, worktree=%d, index=%d",
opts->checkout_worktree, opts->checkout_index);
return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
return run_add_interactive(rev, patch_mode, &opts->pathspec);
}
repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);

View file

@ -59,6 +59,13 @@ test_expect_success PERL 'git checkout -p HEAD with change already staged' '
verify_state dir/foo head head
'
test_expect_success PERL 'git checkout -p HEAD^...' '
# the third n is to get out in case it mistakenly does not apply
test_write_lines n y n | git checkout -p HEAD^... &&
verify_saved_state bar &&
verify_state dir/foo parent parent
'
test_expect_success PERL 'git checkout -p HEAD^' '
# the third n is to get out in case it mistakenly does not apply
test_write_lines n y n | git checkout -p HEAD^ &&

View file

@ -60,6 +60,14 @@ test_expect_success PERL 'git restore -p --source=HEAD^' '
verify_state dir/foo parent index
'
test_expect_success PERL 'git restore -p --source=HEAD^...' '
set_state dir/foo work index &&
# the third n is to get out in case it mistakenly does not apply
test_write_lines n y n | git restore -p --source=HEAD^... &&
verify_saved_state bar &&
verify_state dir/foo parent index
'
test_expect_success PERL 'git restore -p handles deletion' '
set_state dir/foo work index &&
rm dir/foo &&