Skip to content

Commit 414d924

Browse files
Denton-Lgitster
authored andcommitted
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. While developing the patches, 'master' is also developed further and it is sometimes not the best idea to keep rebasing on top of 'master', but to keep the base commit as-is. In addition to this, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <[email protected]> Helped-by: Junio C Hamano <[email protected]> Helped-by: Ævar Arnfjörð Bjarmason <[email protected]> Helped-by: Johannes Schindelin <[email protected]> Signed-off-by: Denton Liu <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6330209 commit 414d924

File tree

6 files changed

+126
-10
lines changed

6 files changed

+126
-10
lines changed

Documentation/git-rebase.txt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip
88
SYNOPSIS
99
--------
1010
[verse]
11-
'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
12-
[<upstream> [<branch>]]
11+
'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]
12+
[--onto <newbase> | --keep-base] [<upstream> [<branch>]]
1313
'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
1414
--root [<branch>]
1515
'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
@@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the
217217
merge base of A and B if there is exactly one merge base. You can
218218
leave out at most one of A and B, in which case it defaults to HEAD.
219219

220+
--keep-base::
221+
Set the starting point at which to create the new commits to the
222+
merge base of <upstream> <branch>. Running
223+
'git rebase --keep-base <upstream> <branch>' is equivalent to
224+
running 'git rebase --onto <upstream>... <upstream>'.
225+
+
226+
This option is useful in the case where one is developing a feature on
227+
top of an upstream branch. While the feature is being worked on, the
228+
upstream branch may advance and it may not be the best idea to keep
229+
rebasing on top of the upstream but to keep the base commit as-is.
230+
+
231+
Although both this option and --fork-point find the merge base between
232+
<upstream> and <branch>, this option uses the merge base as the _starting
233+
point_ on which new commits will be created, whereas --fork-point uses
234+
the merge base to determine the _set of commits_ which will be rebased.
235+
+
236+
See also INCOMPATIBLE OPTIONS below.
237+
220238
<upstream>::
221239
Upstream branch to compare against. May be any valid commit,
222240
not just an existing branch name. Defaults to the configured
@@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback.
369387
+
370388
If either <upstream> or --root is given on the command line, then the
371389
default is `--no-fork-point`, otherwise the default is `--fork-point`.
390+
+
391+
If your branch was based on <upstream> but <upstream> was rewound and
392+
your branch contains commits which were dropped, this option can be used
393+
with `--keep-base` in order to drop those commits from your branch.
372394

373395
--ignore-whitespace::
374396
--whitespace=<option>::
@@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible:
545567
* --preserve-merges and --rebase-merges
546568
* --rebase-merges and --strategy
547569
* --rebase-merges and --strategy-option
570+
* --keep-base and --onto
571+
* --keep-base and --root
548572

549573
BEHAVIORAL DIFFERENCES
550574
-----------------------
@@ -870,7 +894,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful
870894
--interactive` will be **resurrected**!
871895

872896
The idea is to manually tell 'git rebase' "where the old 'subsystem'
873-
ended and your 'topic' began", that is, what the old merge-base
897+
ended and your 'topic' began", that is, what the old merge base
874898
between them was. You will have to find a way to name the last commit
875899
of the old 'subsystem', for example:
876900

builtin/rebase.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
#include "rebase-interactive.h"
3030

3131
static char const * const builtin_rebase_usage[] = {
32-
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
33-
"[<upstream>] [<branch>]"),
32+
N_("git rebase [-i] [options] [--exec <cmd>] "
33+
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
3434
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
3535
"--root [<branch>]"),
3636
N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@ -1396,6 +1396,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
13961396
struct rebase_options options = REBASE_OPTIONS_INIT;
13971397
const char *branch_name;
13981398
int ret, flags, total_argc, in_progress = 0;
1399+
int keep_base = 0;
13991400
int ok_to_skip_pre_rebase = 0;
14001401
struct strbuf msg = STRBUF_INIT;
14011402
struct strbuf revisions = STRBUF_INIT;
@@ -1414,6 +1415,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
14141415
OPT_STRING(0, "onto", &options.onto_name,
14151416
N_("revision"),
14161417
N_("rebase onto given branch instead of upstream")),
1418+
OPT_BOOL(0, "keep-base", &keep_base,
1419+
N_("use the merge-base of upstream and branch as the current base")),
14171420
OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
14181421
N_("allow pre-rebase hook to run")),
14191422
OPT_NEGBIT('q', "quiet", &options.flags,
@@ -1567,6 +1570,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
15671570
warning(_("git rebase --preserve-merges is deprecated. "
15681571
"Use --rebase-merges instead."));
15691572

1573+
if (keep_base) {
1574+
if (options.onto_name)
1575+
die(_("cannot combine '--keep-base' with '--onto'"));
1576+
if (options.root)
1577+
die(_("cannot combine '--keep-base' with '--root'"));
1578+
}
1579+
15701580
if (action != ACTION_NONE && !in_progress)
15711581
die(_("No rebase in progress?"));
15721582
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1902,12 +1912,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
19021912
}
19031913

19041914
/* Make sure the branch to rebase onto is valid. */
1905-
if (!options.onto_name)
1915+
if (keep_base) {
1916+
strbuf_reset(&buf);
1917+
strbuf_addstr(&buf, options.upstream_name);
1918+
strbuf_addstr(&buf, "...");
1919+
options.onto_name = xstrdup(buf.buf);
1920+
} else if (!options.onto_name)
19061921
options.onto_name = options.upstream_name;
19071922
if (strstr(options.onto_name, "...")) {
1908-
if (get_oid_mb(options.onto_name, &merge_base) < 0)
1909-
die(_("'%s': need exactly one merge base"),
1910-
options.onto_name);
1923+
if (get_oid_mb(options.onto_name, &merge_base) < 0) {
1924+
if (keep_base)
1925+
die(_("'%s': need exactly one merge base with branch"),
1926+
options.upstream_name);
1927+
else
1928+
die(_("'%s': need exactly one merge base"),
1929+
options.onto_name);
1930+
}
19111931
options.onto = lookup_commit_or_die(&merge_base,
19121932
options.onto_name);
19131933
} else {

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2030,7 +2030,7 @@ _git_rebase ()
20302030
--autosquash --no-autosquash
20312031
--fork-point --no-fork-point
20322032
--autostash --no-autostash
2033-
--verify --no-verify
2033+
--verify --no-verify --keep-base
20342034
--keep-empty --root --force-rebase --no-ff
20352035
--rerere-autoupdate
20362036
--exec

t/t3416-rebase-onto-threedots.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '
9999
git checkout side &&
100100
git reset --hard K &&
101101
102+
set_fake_editor &&
102103
test_must_fail git rebase -i --onto master...side J
103104
'
104105

106+
test_expect_success 'rebase --keep-base --onto incompatible' '
107+
test_must_fail git rebase --keep-base --onto master...
108+
'
109+
110+
test_expect_success 'rebase --keep-base --root incompatible' '
111+
test_must_fail git rebase --keep-base --root
112+
'
113+
114+
test_expect_success 'rebase --keep-base master from topic' '
115+
git reset --hard &&
116+
git checkout topic &&
117+
git reset --hard G &&
118+
119+
git rebase --keep-base master &&
120+
git rev-parse C >base.expect &&
121+
git merge-base master HEAD >base.actual &&
122+
test_cmp base.expect base.actual &&
123+
124+
git rev-parse HEAD~2 >actual &&
125+
git rev-parse C^0 >expect &&
126+
test_cmp expect actual
127+
'
128+
129+
test_expect_success 'rebase --keep-base master from side' '
130+
git reset --hard &&
131+
git checkout side &&
132+
git reset --hard K &&
133+
134+
test_must_fail git rebase --keep-base master
135+
'
136+
137+
test_expect_success 'rebase -i --keep-base master from topic' '
138+
git reset --hard &&
139+
git checkout topic &&
140+
git reset --hard G &&
141+
142+
set_fake_editor &&
143+
EXPECT_COUNT=2 git rebase -i --keep-base master &&
144+
git rev-parse C >base.expect &&
145+
git merge-base master HEAD >base.actual &&
146+
test_cmp base.expect base.actual &&
147+
148+
git rev-parse HEAD~2 >actual &&
149+
git rev-parse C^0 >expect &&
150+
test_cmp expect actual
151+
'
152+
153+
test_expect_success 'rebase -i --keep-base master from side' '
154+
git reset --hard &&
155+
git checkout side &&
156+
git reset --hard K &&
157+
158+
set_fake_editor &&
159+
test_must_fail git rebase -i --keep-base master
160+
'
161+
105162
test_done

t/t3431-rebase-fork-point.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,15 @@ test_rebase () {
4343

4444
test_rebase 'G F E D B A'
4545
test_rebase 'G F D B A' --onto D
46+
test_rebase 'G F B A' --keep-base
4647
test_rebase 'G F C E D B A' --no-fork-point
4748
test_rebase 'G F C D B A' --no-fork-point --onto D
49+
test_rebase 'G F C B A' --no-fork-point --keep-base
4850
test_rebase 'G F E D B A' --fork-point refs/heads/master
4951
test_rebase 'G F D B A' --fork-point --onto D refs/heads/master
52+
test_rebase 'G F B A' --fork-point --keep-base refs/heads/master
5053
test_rebase 'G F C E D B A' refs/heads/master
5154
test_rebase 'G F C D B A' --onto D refs/heads/master
55+
test_rebase 'G F C B A' --keep-base refs/heads/master
5256

5357
test_done

t/t3432-rebase-fast-forward.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,15 @@ test_rebase_same_head success noop same success noop-force same master
7575
test_rebase_same_head success noop same success noop-force diff --onto B B
7676
test_rebase_same_head success noop same success noop-force diff --onto B... B
7777
test_rebase_same_head success noop same success noop-force same --onto master... master
78+
test_rebase_same_head success noop same success noop-force same --keep-base master
79+
test_rebase_same_head success noop same success noop-force same --keep-base
7880
test_rebase_same_head success noop same success noop-force same --no-fork-point
81+
test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
7982
test_rebase_same_head success noop same success work same --fork-point master
8083
test_rebase_same_head success noop same success work diff --fork-point --onto B B
8184
test_rebase_same_head success noop same success work diff --fork-point --onto B... B
8285
test_rebase_same_head success noop same success work same --fork-point --onto master... master
86+
test_rebase_same_head success noop same success work same --keep-base --keep-base master
8387

8488
test_expect_success 'add work same to side' '
8589
test_commit E
@@ -91,11 +95,15 @@ test_rebase_same_head success noop same success noop-force same master
9195
test_rebase_same_head success noop same success noop-force diff --onto B B
9296
test_rebase_same_head success noop same success noop-force diff --onto B... B
9397
test_rebase_same_head success noop same success noop-force same --onto master... master
98+
test_rebase_same_head success noop same success noop-force same --keep-base master
99+
test_rebase_same_head success noop same success noop-force same --keep-base
94100
test_rebase_same_head success noop same success noop-force same --no-fork-point
101+
test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
95102
test_rebase_same_head success noop same success work same --fork-point master
96103
test_rebase_same_head success noop same success work diff --fork-point --onto B B
97104
test_rebase_same_head success noop same success work diff --fork-point --onto B... B
98105
test_rebase_same_head success noop same success work same --fork-point --onto master... master
106+
test_rebase_same_head success noop same success work same --fork-point --keep-base master
99107

100108
test_expect_success 'add work same to upstream' '
101109
git checkout master &&
@@ -107,8 +115,11 @@ changes='our and their changes'
107115
test_rebase_same_head success noop same success noop-force diff --onto B B
108116
test_rebase_same_head success noop same success noop-force diff --onto B... B
109117
test_rebase_same_head success noop same success work diff --onto master... master
118+
test_rebase_same_head success noop same success work diff --keep-base master
119+
test_rebase_same_head success noop same success work diff --keep-base
110120
test_rebase_same_head failure work same success work diff --fork-point --onto B B
111121
test_rebase_same_head failure work same success work diff --fork-point --onto B... B
112122
test_rebase_same_head success noop same success work diff --fork-point --onto master... master
123+
test_rebase_same_head success noop same success work diff --fork-point --keep-base master
113124

114125
test_done

0 commit comments

Comments
 (0)