Skip to content

Commit 8809649

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'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, 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 4469851 commit 8809649

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
@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.
364382
+
365383
If either <upstream> or --root is given on the command line, then the
366384
default is `--no-fork-point`, otherwise the default is `--fork-point`.
385+
+
386+
If your branch was based on <upstream> but <upstream> was rewound and
387+
your branch contains commits which were dropped, this option can be used
388+
with `--keep-base` in order to drop those commits from your branch.
367389

368390
--ignore-whitespace::
369391
--whitespace=<option>::
@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:
539561
* --preserve-merges and --rebase-merges
540562
* --rebase-merges and --strategy
541563
* --rebase-merges and --strategy-option
564+
* --keep-base and --onto
565+
* --keep-base and --root
542566

543567
BEHAVIORAL DIFFERENCES
544568
-----------------------
@@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful
863887
--interactive` will be **resurrected**!
864888

865889
The idea is to manually tell 'git rebase' "where the old 'subsystem'
866-
ended and your 'topic' began", that is, what the old merge-base
890+
ended and your 'topic' began", that is, what the old merge base
867891
between them was. You will have to find a way to name the last commit
868892
of the old 'subsystem', for example:
869893

builtin/rebase.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
#include "branch.h"
2828

2929
static char const * const builtin_rebase_usage[] = {
30-
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
31-
"[<upstream>] [<branch>]"),
30+
N_("git rebase [-i] [options] [--exec <cmd>] "
31+
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
3232
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
3333
"--root [<branch>]"),
3434
N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
10391039
};
10401040
const char *branch_name;
10411041
int ret, flags, total_argc, in_progress = 0;
1042+
int keep_base = 0;
10421043
int ok_to_skip_pre_rebase = 0;
10431044
struct strbuf msg = STRBUF_INIT;
10441045
struct strbuf revisions = STRBUF_INIT;
@@ -1072,6 +1073,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
10721073
OPT_STRING(0, "onto", &options.onto_name,
10731074
N_("revision"),
10741075
N_("rebase onto given branch instead of upstream")),
1076+
OPT_BOOL(0, "keep-base", &keep_base,
1077+
N_("use the merge-base of upstream and branch as the current base")),
10751078
OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
10761079
N_("allow pre-rebase hook to run")),
10771080
OPT_NEGBIT('q', "quiet", &options.flags,
@@ -1238,6 +1241,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
12381241
usage_with_options(builtin_rebase_usage,
12391242
builtin_rebase_options);
12401243

1244+
if (keep_base) {
1245+
if (options.onto_name)
1246+
die(_("cannot combine '--keep-base' with '--onto'"));
1247+
if (options.root)
1248+
die(_("cannot combine '--keep-base' with '--root'"));
1249+
}
1250+
12411251
if (action != NO_ACTION && !in_progress)
12421252
die(_("No rebase in progress?"));
12431253
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1562,12 +1572,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
15621572
}
15631573

15641574
/* Make sure the branch to rebase onto is valid. */
1565-
if (!options.onto_name)
1575+
if (keep_base) {
1576+
strbuf_reset(&buf);
1577+
strbuf_addstr(&buf, options.upstream_name);
1578+
strbuf_addstr(&buf, "...");
1579+
options.onto_name = xstrdup(buf.buf);
1580+
} else if (!options.onto_name)
15661581
options.onto_name = options.upstream_name;
15671582
if (strstr(options.onto_name, "...")) {
1568-
if (get_oid_mb(options.onto_name, &merge_base) < 0)
1569-
die(_("'%s': need exactly one merge base"),
1570-
options.onto_name);
1583+
if (get_oid_mb(options.onto_name, &merge_base) < 0) {
1584+
if (keep_base)
1585+
die(_("'%s': need exactly one merge base with branch"),
1586+
options.upstream_name);
1587+
else
1588+
die(_("'%s': need exactly one merge base"),
1589+
options.onto_name);
1590+
}
15711591
options.onto = lookup_commit_or_die(&merge_base,
15721592
options.onto_name);
15731593
} else {

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2027,7 +2027,7 @@ _git_rebase ()
20272027
--autosquash --no-autosquash
20282028
--fork-point --no-fork-point
20292029
--autostash --no-autostash
2030-
--verify --no-verify
2030+
--verify --no-verify --keep-base
20312031
--keep-empty --root --force-rebase --no-ff
20322032
--rerere-autoupdate
20332033
--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
@@ -33,11 +33,15 @@ test_rebase_same_head success master
3333
test_rebase_same_head success --onto B B
3434
test_rebase_same_head success --onto B... B
3535
test_rebase_same_head success --onto master... master
36+
test_rebase_same_head success --keep-base master
37+
test_rebase_same_head success --keep-base
3638
test_rebase_same_head success --no-fork-point
39+
test_rebase_same_head success --keep-base --no-fork-point
3740
test_rebase_same_head success --fork-point master
3841
test_rebase_same_head success --fork-point --onto B B
3942
test_rebase_same_head success --fork-point --onto B... B
4043
test_rebase_same_head success --fork-point --onto master... master
44+
test_rebase_same_head success --fork-point --keep-base master
4145

4246
test_expect_success 'add work to side' '
4347
test_commit E
@@ -49,11 +53,15 @@ test_rebase_same_head success master
4953
test_rebase_same_head success --onto B B
5054
test_rebase_same_head success --onto B... B
5155
test_rebase_same_head success --onto master... master
56+
test_rebase_same_head success --keep-base master
57+
test_rebase_same_head success --keep-base
5258
test_rebase_same_head success --no-fork-point
59+
test_rebase_same_head success --keep-base --no-fork-point
5360
test_rebase_same_head success --fork-point master
5461
test_rebase_same_head success --fork-point --onto B B
5562
test_rebase_same_head success --fork-point --onto B... B
5663
test_rebase_same_head success --fork-point --onto master... master
64+
test_rebase_same_head success --fork-point --keep-base master
5765

5866
test_expect_success 'add work to upstream' '
5967
git checkout master &&
@@ -65,8 +73,11 @@ changes='our and their changes'
6573
test_rebase_same_head success --onto B B
6674
test_rebase_same_head success --onto B... B
6775
test_rebase_same_head success --onto master... master
76+
test_rebase_same_head success --keep-base master
77+
test_rebase_same_head success --keep-base
6878
test_rebase_same_head success --fork-point --onto B B
6979
test_rebase_same_head success --fork-point --onto B... B
7080
test_rebase_same_head success --fork-point --onto master... master
81+
test_rebase_same_head success --fork-point --keep-base master
7182

7283
test_done

0 commit comments

Comments
 (0)