Merge branch 'dl/checkout-p-merge-base'

"git checkout -p A...B [-- <path>]" did not work, even though the
same command without "-p" correctly used the merge-base between
commits A and B.

* dl/checkout-p-merge-base:
  t2016: add a NEEDSWORK about the PERL prerequisite
  add-patch: add NEEDSWORK about comparing commits
  Doc: document "A...B" form for <tree-ish> in checkout and switch
  builtin/checkout: fix `git checkout -p HEAD...` bug
This commit is contained in:
Junio C Hamano
2020-10-27 15:09:50 -07:00
7 changed files with 56 additions and 1 deletions

View File

@ -351,6 +351,10 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
<tree-ish>:: <tree-ish>::
Tree to checkout from (when paths are given). If not specified, Tree to checkout from (when paths are given). If not specified,
the index will be used. the index will be used.
+
As a special case, you may use `"A...B"` as a shortcut for the
merge base of `A` and `B` if there is exactly one merge base. You can
leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
\--:: \--::
Do not interpret any more arguments as options. Do not interpret any more arguments as options.

View File

@ -40,6 +40,10 @@ OPTIONS
+ +
If not specified, the contents are restored from `HEAD` if `--staged` is If not specified, the contents are restored from `HEAD` if `--staged` is
given, otherwise from the index. given, otherwise from the index.
+
As a special case, you may use `"A...B"` as a shortcut for the
merge base of `A` and `B` if there is exactly one merge base. You can
leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
-p:: -p::
--patch:: --patch::

View File

@ -1695,6 +1695,14 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
if (mode == ADD_P_STASH) if (mode == ADD_P_STASH)
s.mode = &patch_mode_stash; s.mode = &patch_mode_stash;
else if (mode == ADD_P_RESET) { else if (mode == ADD_P_RESET) {
/*
* NEEDSWORK: Instead of comparing to the literal "HEAD",
* compare the commit objects instead so that other ways of
* saying the same thing (such as "@") are also handled
* appropriately.
*
* This applies to the cases below too.
*/
if (!revision || !strcmp(revision, "HEAD")) if (!revision || !strcmp(revision, "HEAD"))
s.mode = &patch_mode_reset_head; s.mode = &patch_mode_reset_head;
else else

View File

@ -471,6 +471,19 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->patch_mode) { if (opts->patch_mode) {
const char *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) if (opts->checkout_index && opts->checkout_worktree)
patch_mode = "--patch=checkout"; patch_mode = "--patch=checkout";
@ -481,7 +494,7 @@ static int checkout_paths(const struct checkout_opts *opts,
else else
BUG("either flag must have been set, worktree=%d, index=%d", BUG("either flag must have been set, worktree=%d, index=%d",
opts->checkout_worktree, opts->checkout_index); 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); repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);

View File

@ -1830,6 +1830,13 @@ sub process_args {
$arg = shift @ARGV or die __("missing --"); $arg = shift @ARGV or die __("missing --");
if ($arg ne '--') { if ($arg ne '--') {
$patch_mode_revision = $arg; $patch_mode_revision = $arg;
# NEEDSWORK: Instead of comparing to the literal "HEAD",
# compare the commit objects instead so that other ways of
# saying the same thing (such as "@") are also handled
# appropriately.
#
# This applies to the cases below too.
$patch_mode = ($arg eq 'HEAD' ? $patch_mode = ($arg eq 'HEAD' ?
'reset_head' : 'reset_nothead'); 'reset_head' : 'reset_nothead');
$arg = shift @ARGV or die __("missing --"); $arg = shift @ARGV or die __("missing --");

View File

@ -18,6 +18,10 @@ test_expect_success PERL 'setup' '
# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar' # note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'
# NEEDSWORK: Since the builtin add-p is used when $GIT_TEST_ADD_I_USE_BUILTIN
# is given, we should replace the PERL prerequisite with an ADD_I prerequisite
# which first checks if $GIT_TEST_ADD_I_USE_BUILTIN is defined before checking
# PERL.
test_expect_success PERL 'saying "n" does nothing' ' test_expect_success PERL 'saying "n" does nothing' '
set_and_save_state dir/foo work head && set_and_save_state dir/foo work head &&
test_write_lines n n | git checkout -p && test_write_lines n n | git checkout -p &&
@ -59,6 +63,13 @@ test_expect_success PERL 'git checkout -p HEAD with change already staged' '
verify_state dir/foo head head 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^' ' test_expect_success PERL 'git checkout -p HEAD^' '
# the third n is to get out in case it mistakenly does not apply # the third n is to get out in case it mistakenly does not apply
test_write_lines n y n | git checkout -p HEAD^ && 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 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' ' test_expect_success PERL 'git restore -p handles deletion' '
set_state dir/foo work index && set_state dir/foo work index &&
rm dir/foo && rm dir/foo &&