]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/checkout: fix `git checkout -p HEAD...` bug
authorDenton Liu <liu.denton@gmail.com>
Wed, 7 Oct 2020 07:56:15 +0000 (00:56 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 7 Oct 2020 16:49:05 +0000 (09:49 -0700)
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>
builtin/checkout.c
t/t2016-checkout-patch.sh
t/t2071-restore-patch.sh

index af849c644fec1852845b1dfdb6ce8daa903e0fb5..9a33b005ccd194da3e86c7139a5de33d36f5083e 100644 (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);
index 47aeb0b1674c01fd35e3aef759756348026a04bb..999620e507631a68938f396b0d9b420450fe6755 100755 (executable)
@@ -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^ &&
index 98b2476e7c79f74745d8276035f94fe52a7133e5..b5c5c0ff7e37ced9b6b50a06e4ec3e30c536cd53 100755 (executable)
@@ -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 &&