From 8daf7c9e9762930120a97816f74a4278f1673068 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 28 Feb 2019 15:05:57 +0100 Subject: [PATCH 1/4] built-in rebase: no need to check out `onto` twice In the case that the rebase boils down to a fast-forward, the built-in rebase reset the working tree twice: once to start the rebase at `onto`, then realizing that the original (pre-rebase) HEAD was an ancestor and we basically already fast-forwarded to the post-rebase HEAD, `reset_head()` was called to update the original ref and to point HEAD back to it. That second `reset_head()` call does not need to touch the working tree, though, as it does not change the actual tip commit (and therefore the working tree should stay unchanged anyway): only the ref needs to be updated (because the rebase detached the HEAD, and we want to go back to the branch on which the rebase was started). But that second `reset_head()` was called without the flag to leave the working tree alone (the reason: when that call was introduced, that flag was not yet even thought of). Let's avoid that unnecessary work by passing that flag. Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7c7bc13e91..3031b7a540 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1776,8 +1776,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(NULL, "Fast-forwarded", options.head_name, 0, - "HEAD", msg.buf); + reset_head(NULL, "Fast-forwarded", options.head_name, + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf); strbuf_release(&msg); ret = !!finish_rebase(&options); goto cleanup; From cfe33f7fa1a9ac42ef9e7557acb03cb434ace996 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 28 Feb 2019 15:04:50 +0100 Subject: [PATCH 2/4] built-in rebase: use the correct reflog when switching branches By mistake, we used the reflog intended for ORIG_HEAD. Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 3031b7a540..8f073f5f76 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -476,7 +476,7 @@ reset_head_refs: detach_head ? REF_NO_DEREF : 0, UPDATE_REFS_MSG_ON_ERR); else { - ret = update_ref(reflog_orig_head, switch_to_branch, oid, + ret = update_ref(reflog_head, switch_to_branch, oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); if (!ret) ret = create_symref("HEAD", switch_to_branch, From 37e29cb7a048f1dd30b81c71c09ff67be66f5681 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 28 Feb 2019 09:33:29 +0100 Subject: [PATCH 3/4] built-in rebase: demonstrate that ORIG_HEAD is not set correctly The ORIG_HEAD pseudo ref is supposed to refer to the original, pre-rebase state after a successful rebase. Let's add a regression test to prove that this regressed: With GIT_TEST_REBASE_USE_BUILTIN=false, this test case passes, with GIT_TEST_REBASE_USE_BUILTIN=true (or unset), it fails. Reported by Nazri Ramliy. Signed-off-by: Johannes Schindelin --- t/t3400-rebase.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 3e73f7584c..7e8d5bb200 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -59,6 +59,14 @@ test_expect_success 'rebase against master' ' git rebase master ' +test_expect_failure 'rebase sets ORIG_HEAD to pre-rebase state' ' + git checkout -b orig-head topic && + pre="$(git rev-parse --verify HEAD)" && + git rebase master && + test_cmp_rev "$pre" ORIG_HEAD && + ! test_cmp_rev "$pre" HEAD +' + test_expect_success 'rebase, with and specified as :/quuxery' ' test_when_finished "git branch -D torebase" && git checkout -b torebase my-topic-branch^ && From bb6efa86d2ebfd550b39960e839f4508a73928c5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 28 Feb 2019 14:57:25 +0100 Subject: [PATCH 4/4] built-in rebase: set ORIG_HEAD just once, before the rebase Technically, the scripted version set ORIG_HEAD only in two spots (which really could have been one, because it called `git checkout $onto^0` to start the rebase and also if it could take a shortcut, and in both cases it called `git update-ref $orig_head`). Practically, it *implicitly* reset ORIG_HEAD whenever `git reset --hard` was called. However, what we really want is that it is set exactly once, at the beginning of the rebase. So let's do that. Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 33 +++++++++++++++++++-------------- t/t3400-rebase.sh | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 8f073f5f76..156a6c0366 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -369,6 +369,7 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) #define RESET_HEAD_HARD (1<<1) #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) #define RESET_HEAD_REFS_ONLY (1<<3) +#define RESET_ORIG_HEAD (1<<4) static int reset_head(struct object_id *oid, const char *action, const char *switch_to_branch, unsigned flags, @@ -378,6 +379,7 @@ static int reset_head(struct object_id *oid, const char *action, unsigned reset_hard = flags & RESET_HEAD_HARD; unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = flags & RESET_ORIG_HEAD; struct object_id head_oid; struct tree_desc desc[2] = { { NULL }, { NULL } }; struct lock_file lock = LOCK_INIT; @@ -454,18 +456,21 @@ reset_head_refs: strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); prefix_len = msg.len; - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, - UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + if (update_orig_head) { + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, + old_orig, 0, UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + } + if (!reflog_head) { strbuf_setlen(&msg, prefix_len); strbuf_addstr(&msg, "updating HEAD"); @@ -1760,8 +1765,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf)) + RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK | + RESET_ORIG_HEAD, NULL, msg.buf)) die(_("Could not detach HEAD")); strbuf_release(&msg); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 7e8d5bb200..460d0523be 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -59,7 +59,7 @@ test_expect_success 'rebase against master' ' git rebase master ' -test_expect_failure 'rebase sets ORIG_HEAD to pre-rebase state' ' +test_expect_success 'rebase sets ORIG_HEAD to pre-rebase state' ' git checkout -b orig-head topic && pre="$(git rev-parse --verify HEAD)" && git rebase master &&