rebase --apply: set ORIG_HEAD correctly

At the start of a rebase, ORIG_HEAD is updated to the tip of the branch being rebased. Unfortunately reset_head() always uses the current value of HEAD for this which is incorrect if the rebase is started with "git rebase <upstream> <branch>" as in that case ORIG_HEAD should be updated to <branch>. This only affects the "apply" backend as the "merge" backend does not yet use reset_head() for the initial checkout. Fix this by passing in orig_head when calling reset_head() and add some regression tests. 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 cd1528ef8ef9847fc27cff4016bf073f04729504
4 files changed +34 -1
builtin/rebase.c
+1
@@ -1769,6 +1769,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1769 strbuf_addf(&msg, "%s: checkout %s",
1770 getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
1771 ropts.oid = &options.onto->object.oid;
1772 + ropts.orig_head = &options.orig_head,
1773 ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
1774 RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
1775 ropts.head_msg = msg.buf;
reset.c
+3 -1
@@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts,
15 unsigned detach_head = opts->flags & RESET_HEAD_DETACH;
16 unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
17 unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
18 + const struct object_id *orig_head = opts->orig_head;
19 const char *switch_to_branch = opts->branch;
20 const char *reflog_branch = opts->branch_msg;
21 const char *reflog_head = opts->head_msg;
@@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts,
44 strbuf_addstr(&msg, "updating ORIG_HEAD");
45 reflog_orig_head = msg.buf;
46 }
46 - update_ref(reflog_orig_head, "ORIG_HEAD", head,
47 + update_ref(reflog_orig_head, "ORIG_HEAD",
48 + orig_head ? orig_head : head,
49 old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
50 } else if (old_orig)
51 delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
reset.h
+4
@@ -22,6 +22,10 @@ struct reset_head_opts {
22 * The commit to checkout/reset to. Defaults to HEAD.
23 */
24 const struct object_id *oid;
25 + /*
26 + * Optional value to set ORIG_HEAD. Defaults to HEAD.
27 + */
28 + const struct object_id *orig_head;
29 /*
30 * Optional branch to switch to.
31 */
t/t3418-rebase-continue.sh
+26
@@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas
308 test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
309 '
310
311 +test_orig_head_helper () {
312 + test_when_finished 'git rebase --abort &&
313 + git checkout topic &&
314 + git reset --hard commit-new-file-F2-on-topic-branch' &&
315 + git update-ref -d ORIG_HEAD &&
316 + test_must_fail git rebase "$@" &&
317 + test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch
318 +}
319 +
320 +test_orig_head () {
321 + type=$1
322 + test_expect_success "rebase $type sets ORIG_HEAD correctly" '
323 + git checkout topic &&
324 + git reset --hard commit-new-file-F2-on-topic-branch &&
325 + test_orig_head_helper $type main
326 + '
327 +
328 + test_expect_success "rebase $type <upstream> <branch> sets ORIG_HEAD correctly" '
329 + git checkout main &&
330 + test_orig_head_helper $type main topic
331 + '
332 +}
333 +
334 +test_orig_head --apply
335 +test_orig_head --merge
336 +
337 test_done