rebase: do not remove untracked files on checkout

If "git rebase [--apply|--merge] <upstream> <branch>" detects that <upstream> is an ancestor of <branch> then it will fast-forward and checkout <branch>. Normally a checkout or picking a commit during a rebase will refuse to overwrite untracked files, however rebase does overwrite untracked files when checking out <branch>. The fix is to only set reset in `unpack_tree_opts` if flags contains `RESET_HEAD_HARD`. t5403 may seem like an odd home for the new test but it will be extended in the next commit to check that the post-checkout hook is not run when the checkout fails. The test for `!detach_head` dates back to the original implementation of reset_head() in ac7f467fef ("builtin/rebase: support running "git rebase <upstream>"", 2018-08-07) and was correct until e65123a71d ("builtin rebase: support `git rebase <upstream> <switch-to>`", 2018-09-04) started using reset_head() to checkout <switch-to> when fast-forwarding. Note that 480d3d6bf9 ("Change unpack_trees' 'reset' flag into an enum", 2021-09-27) also fixes this bug as it changes reset_head() to never remove untracked files. I think this fix is still worthwhile as it makes it clear that the same settings are used for detached and non-detached checkouts. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Phillip Wood committed Jan 26, 2022 at 13:05 UTC ab2fba0868860e610619f885031217d9cc63097a
2 files changed +11 -1
reset.c
+1 -1
@@ -59,7 +59,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
59 unpack_tree_opts.merge = 1;
60 unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
61 init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
62 - if (!detach_head)
62 + if (reset_hard)
63 unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
64
65 if (repo_read_index_unmerged(r) < 0) {
t/t5403-post-checkout-hook.sh
+10
@@ -85,6 +85,16 @@ test_rebase () {
85 test_cmp_rev three $new &&
86 test $flag = 1
87 '
88 +
89 + test_expect_success "rebase $args checkout does not remove untracked files" '
90 + test_when_finished "test_might_fail git rebase --abort" &&
91 + git update-ref refs/heads/rebase-fast-forward three &&
92 + git checkout two &&
93 + echo untracked >three.t &&
94 + test_when_finished "rm three.t" &&
95 + test_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&
96 + grep "untracked working tree files would be overwritten by checkout" err
97 +'
98 }
99
100 test_rebase --apply &&