Skip to content

Commit 10572de

Browse files
Denton-Lgitster
authored andcommitted
rebase: fast-forward --onto in more cases
Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Helped-by: Phillip Wood <[email protected]> Signed-off-by: Denton Liu <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 526c03b commit 10572de

File tree

4 files changed

+24
-13
lines changed

4 files changed

+24
-13
lines changed

builtin/rebase.c

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to)
895895
return 1;
896896
}
897897

898-
static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
899-
struct object_id *merge_base)
898+
static int can_fast_forward(struct commit *onto, struct commit *upstream,
899+
struct object_id *head_oid, struct object_id *merge_base)
900900
{
901901
struct commit *head = lookup_commit(the_repository, head_oid);
902902
struct commit_list *merge_bases = NULL;
@@ -915,6 +915,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
915915
if (!oideq(merge_base, &onto->object.oid))
916916
goto done;
917917

918+
if (!upstream)
919+
goto done;
920+
921+
free_commit_list(merge_bases);
922+
merge_bases = get_merge_bases(upstream, head);
923+
if (!merge_bases || merge_bases->next)
924+
goto done;
925+
926+
if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
927+
goto done;
928+
918929
res = 1;
919930

920931
done:
@@ -1688,13 +1699,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
16881699

16891700
/*
16901701
* Check if we are already based on onto with linear history,
1691-
* but this should be done only when upstream and onto are the same
1692-
* and if this is not an interactive rebase.
1702+
* in which case we could fast-forward without replacing the commits
1703+
* with new commits recreated by replaying their changes. This
1704+
* optimization must not be done if this is an interactive rebase.
16931705
*/
1694-
if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
1695-
!is_interactive(&options) && !options.restrict_revision &&
1696-
options.upstream &&
1697-
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
1706+
if (can_fast_forward(options.onto, options.upstream, &options.orig_head,
1707+
&merge_base) &&
1708+
!is_interactive(&options) && !options.restrict_revision) {
16981709
int flag;
16991710

17001711
if (!(options.flags & REBASE_FORCE)) {
@@ -1788,7 +1799,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
17881799
strbuf_addf(&msg, "%s: checkout %s",
17891800
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
17901801
if (reset_head(&options.onto->object.oid, "checkout", NULL,
1791-
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
1802+
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
17921803
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
17931804
NULL, msg.buf))
17941805
die(_("Could not detach HEAD"));

t/t3400-rebase.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '
295295
echo two >>init.t &&
296296
git commit -a -m two &&
297297
git tag two &&
298-
test_must_fail git rebase --onto init HEAD^ &&
298+
test_must_fail git rebase -f --onto init HEAD^ &&
299299
GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
300300
grep "show.*$(git rev-parse two)" stderr
301301
)

t/t3404-rebase-interactive.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
10661066
git reset --hard &&
10671067
git checkout conflict-branch &&
10681068
set_fake_editor &&
1069-
test_must_fail git rebase --onto HEAD~2 HEAD~ &&
1069+
test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
10701070
test_must_fail git rebase --edit-todo &&
10711071
git rebase --abort
10721072
'

t/t3432-rebase-fast-forward.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '
6464
changes='our and their changes'
6565
test_rebase_same_head success --onto B B
6666
test_rebase_same_head success --onto B... B
67-
test_rebase_same_head failure --onto master... master
67+
test_rebase_same_head success --onto master... master
6868
test_rebase_same_head failure --fork-point --onto B B
6969
test_rebase_same_head failure --fork-point --onto B... B
70-
test_rebase_same_head failure --fork-point --onto master... master
70+
test_rebase_same_head success --fork-point --onto master... master
7171

7272
test_done

0 commit comments

Comments
 (0)