checkout/restore: refuse unmerging paths unless checking out of the index

Recreating unmerged index entries using resolve-undo data, recreating conflicted working tree files using unmerged index entries, and writing data out of unmerged index entries, make sense only when we are checking paths out of the index and not when we are checking paths out of a tree-ish. Add an extra check to make sure "--merge" and "--ours/--theirs" options are rejected when checking out from a tree-ish, update the document (especially the SYNOPSIS section) to highlight that they are incompatible, and add a few tests to make sure the combination fails. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Jul 31, 2023 at 15:44 UTC 54f98fee5069f50c6e96687504b82a7695c8648a
5 files changed +29 -5
Documentation/git-checkout.txt
+6 -3
@@ -12,8 +12,10 @@ SYNOPSIS
12 'git checkout' [-q] [-f] [-m] --detach [<branch>]
13 'git checkout' [-q] [-f] [-m] [--detach] <commit>
14 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>]
15 -'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...
16 -'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]
15 +'git checkout' [-f] <tree-ish> [--] <pathspec>...
16 +'git checkout' [-f] <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul]
17 +'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>...
18 +'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul]
19 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]
20
21 DESCRIPTION
@@ -260,7 +262,8 @@ and mark the resolved paths with `git add` (or `git rm` if the merge
262 should result in deletion of the path).
263 +
264 When checking out paths from the index, this option lets you recreate
263 -the conflicted merge in the specified paths.
265 +the conflicted merge in the specified paths. This option cannot be
266 +used when checking out paths from a tree-ish.
267 +
268 When switching branches with `--merge`, staged changes may be lost.
269
Documentation/git-restore.txt
+4
@@ -78,6 +78,8 @@ all modified paths.
78 --theirs::
79 When restoring files in the working tree from the index, use
80 stage #2 ('ours') or #3 ('theirs') for unmerged paths.
81 + This option cannot be used when checking out paths from a
82 + tree-ish (i.e. with the `--source` option).
83 +
84 Note that during `git rebase` and `git pull --rebase`, 'ours' and
85 'theirs' may appear swapped. See the explanation of the same options
@@ -87,6 +89,8 @@ in linkgit:git-checkout[1] for details.
89 --merge::
90 When restoring files on the working tree from the index,
91 recreate the conflicted merge in the unmerged paths.
92 + This option cannot be used when checking out paths from a
93 + tree-ish (i.e. with the `--source` option).
94
95 --conflict=<style>::
96 The same as `--merge` option above, but changes the way the
builtin/checkout.c
+9
@@ -520,6 +520,15 @@ static int checkout_paths(const struct checkout_opts *opts,
520 "--merge", "--conflict", "--staged");
521 }
522
523 + /*
524 + * recreating unmerged index entries and writing out data from
525 + * unmerged index entries would make no sense when checking out
526 + * of a tree-ish.
527 + */
528 + if ((opts->merge || opts->writeout_stage) && opts->source_tree)
529 + die(_("'%s', '%s', or '%s' cannot be used when checking out of a tree"),
530 + "--merge", "--ours", "--theirs");
531 +
532 if (opts->patch_mode) {
533 enum add_p_mode patch_mode;
534 const char *rev = new_branch_info->name;
t/t2070-restore.sh
+5 -2
@@ -137,11 +137,14 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '
137 test_must_fail git rev-parse HEAD:new1
138 '
139
140 -test_expect_success 'restore with merge options rejects --staged' '
140 +test_expect_success 'restore with merge options are incompatible with certain options' '
141 for opts in \
142 "--staged --ours" \
143 "--staged --theirs" \
144 "--staged --merge" \
145 + "--source=HEAD --ours" \
146 + "--source=HEAD --theirs" \
147 + "--source=HEAD --merge" \
148 "--staged --conflict=diff3" \
149 "--staged --worktree --ours" \
150 "--staged --worktree --theirs" \
@@ -149,7 +152,7 @@ test_expect_success 'restore with merge options rejects --staged' '
152 "--staged --worktree --conflict=zdiff3"
153 do
154 test_must_fail git restore $opts . 2>err &&
152 - grep "cannot be used with --staged" err || return
155 + grep "cannot be used" err || return
156 done
157 '
158
t/t7201-co.sh
+5
@@ -497,6 +497,11 @@ test_expect_success 'checkout unmerged stage' '
497 test ztheirside = "z$(cat file)"
498 '
499
500 +test_expect_success 'checkout path with --merge from tree-ish is a no-no' '
501 + setup_conflicting_index &&
502 + test_must_fail git checkout -m HEAD -- file
503 +'
504 +
505 test_expect_success 'checkout with --merge' '
506 setup_conflicting_index &&
507 echo "none of the above" >sample &&