rebase: use correct base for --keep-base when a branch is given

--keep-base rebases onto the merge base of the given upstream and the current HEAD regardless of whether a branch is given. This is contrary to the documentation and to the option's intended purpose. Instead, rebase onto the merge base of the given upstream and the given branch. Signed-off-by: Alex Henrie <alexhenrie24@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Alex Henrie committed Apr 20, 2022 at 22:42 UTC 9e5ebe9668a0f152757182b80bae9b66aad11952
3 files changed +60 -29
Documentation/git-rebase.txt
+3 -2
@@ -215,9 +215,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
215
216 --keep-base::
217 Set the starting point at which to create the new commits to the
218 - merge base of <upstream> <branch>. Running
218 + merge base of <upstream> and <branch>. Running
219 'git rebase --keep-base <upstream> <branch>' is equivalent to
220 - running 'git rebase --onto <upstream>... <upstream>'.
220 + running
221 + 'git rebase --onto <upstream>...<branch> <upstream> <branch>'.
222 +
223 This option is useful in the case where one is developing a feature on
224 top of an upstream branch. While the feature is being worked on, the
builtin/rebase.c
+28 -27
@@ -1583,33 +1583,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1583 options.upstream_arg = "--root";
1584 }
1585
1586 - /* Make sure the branch to rebase onto is valid. */
1587 - if (keep_base) {
1588 - strbuf_reset(&buf);
1589 - strbuf_addstr(&buf, options.upstream_name);
1590 - strbuf_addstr(&buf, "...");
1591 - options.onto_name = xstrdup(buf.buf);
1592 - } else if (!options.onto_name)
1593 - options.onto_name = options.upstream_name;
1594 - if (strstr(options.onto_name, "...")) {
1595 - if (get_oid_mb(options.onto_name, &merge_base) < 0) {
1596 - if (keep_base)
1597 - die(_("'%s': need exactly one merge base with branch"),
1598 - options.upstream_name);
1599 - else
1600 - die(_("'%s': need exactly one merge base"),
1601 - options.onto_name);
1602 - }
1603 - options.onto = lookup_commit_or_die(&merge_base,
1604 - options.onto_name);
1605 - } else {
1606 - options.onto =
1607 - lookup_commit_reference_by_name(options.onto_name);
1608 - if (!options.onto)
1609 - die(_("Does not point to a valid commit '%s'"),
1610 - options.onto_name);
1611 - }
1612 -
1586 /*
1587 * If the branch to rebase is given, that is the branch we will rebase
1588 * branch_name -- branch/commit being rebased, or
@@ -1659,6 +1632,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1632 } else
1633 BUG("unexpected number of arguments left to parse");
1634
1635 + /* Make sure the branch to rebase onto is valid. */
1636 + if (keep_base) {
1637 + strbuf_reset(&buf);
1638 + strbuf_addstr(&buf, options.upstream_name);
1639 + strbuf_addstr(&buf, "...");
1640 + strbuf_addstr(&buf, branch_name);
1641 + options.onto_name = xstrdup(buf.buf);
1642 + } else if (!options.onto_name)
1643 + options.onto_name = options.upstream_name;
1644 + if (strstr(options.onto_name, "...")) {
1645 + if (get_oid_mb(options.onto_name, &merge_base) < 0) {
1646 + if (keep_base)
1647 + die(_("'%s': need exactly one merge base with branch"),
1648 + options.upstream_name);
1649 + else
1650 + die(_("'%s': need exactly one merge base"),
1651 + options.onto_name);
1652 + }
1653 + options.onto = lookup_commit_or_die(&merge_base,
1654 + options.onto_name);
1655 + } else {
1656 + options.onto =
1657 + lookup_commit_reference_by_name(options.onto_name);
1658 + if (!options.onto)
1659 + die(_("Does not point to a valid commit '%s'"),
1660 + options.onto_name);
1661 + }
1662 +
1663 if (options.fork_point > 0) {
1664 struct commit *head =
1665 lookup_commit_reference(the_repository,
t/t3416-rebase-onto-threedots.sh
+29
@@ -129,6 +129,20 @@ test_expect_success 'rebase --keep-base main from topic' '
129 test_cmp expect actual
130 '
131
132 +test_expect_success 'rebase --keep-base main topic from main' '
133 + git checkout main &&
134 + git branch -f topic G &&
135 +
136 + git rebase --keep-base main topic &&
137 + git rev-parse C >base.expect &&
138 + git merge-base main HEAD >base.actual &&
139 + test_cmp base.expect base.actual &&
140 +
141 + git rev-parse HEAD~2 >actual &&
142 + git rev-parse C^0 >expect &&
143 + test_cmp expect actual
144 +'
145 +
146 test_expect_success 'rebase --keep-base main from side' '
147 git reset --hard &&
148 git checkout side &&
@@ -153,6 +167,21 @@ test_expect_success 'rebase -i --keep-base main from topic' '
167 test_cmp expect actual
168 '
169
170 +test_expect_success 'rebase -i --keep-base main topic from main' '
171 + git checkout main &&
172 + git branch -f topic G &&
173 +
174 + set_fake_editor &&
175 + EXPECT_COUNT=2 git rebase -i --keep-base main topic &&
176 + git rev-parse C >base.expect &&
177 + git merge-base main HEAD >base.actual &&
178 + test_cmp base.expect base.actual &&
179 +
180 + git rev-parse HEAD~2 >actual &&
181 + git rev-parse C^0 >expect &&
182 + test_cmp expect actual
183 +'
184 +
185 test_expect_success 'rebase -i --keep-base main from side' '
186 git reset --hard &&
187 git checkout side &&