Skip to content

Commit 9234b00

Browse files
committed
Merge branch 'mb/rebase-i-no-ff'
* mb/rebase-i-no-ff: Teach rebase the --no-ff option. Conflicts: git-rebase--interactive.sh t/t3404-rebase-interactive.sh
2 parents 7b1cb5c + b499549 commit 9234b00

File tree

5 files changed

+152
-9
lines changed

5 files changed

+152
-9
lines changed

Documentation/git-rebase.txt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,16 @@ which makes little sense.
274274
-f::
275275
--force-rebase::
276276
Force the rebase even if the current branch is a descendant
277-
of the commit you are rebasing onto. Normally the command will
277+
of the commit you are rebasing onto. Normally non-interactive rebase will
278278
exit with the message "Current branch is up to date" in such a
279279
situation.
280+
Incompatible with the --interactive option.
281+
+
282+
You may find this (or --no-ff with an interactive rebase) helpful after
283+
reverting a topic branch merge, as this option recreates the topic branch with
284+
fresh commits so it can be remerged successfully without needing to "revert
285+
the reversion" (see the
286+
link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
280287

281288
--ignore-whitespace::
282289
--whitespace=<option>::
@@ -316,7 +323,19 @@ which makes little sense.
316323
commit to be modified, and change the action of the moved
317324
commit from `pick` to `squash` (or `fixup`).
318325
+
319-
This option is only valid when '--interactive' option is used.
326+
This option is only valid when the '--interactive' option is used.
327+
328+
--no-ff::
329+
With --interactive, cherry-pick all rebased commits instead of
330+
fast-forwarding over the unchanged ones. This ensures that the
331+
entire history of the rebased branch is composed of new commits.
332+
+
333+
Without --interactive, this is a synonym for --force-rebase.
334+
+
335+
You may find this helpful after reverting a topic branch merge, as this option
336+
recreates the topic branch with fresh commits so it can be remerged
337+
successfully without needing to "revert the reversion" (see the
338+
link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
320339

321340
include::merge-strategies.txt[]
322341

Documentation/howto/revert-a-faulty-merge.txt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ different resolution strategies:
142142
revert of a merge was rebuilt from scratch (i.e. rebasing and fixing,
143143
as you seem to have interpreted), then re-merging the result without
144144
doing anything else fancy would be the right thing to do.
145+
(See the ADDENDUM below for how to rebuild a branch from scratch
146+
without changing its original branching-off point.)
145147

146148
However, there are things to keep in mind when reverting a merge (and
147149
reverting such a revert).
@@ -177,3 +179,91 @@ the answer is: "oops, I really shouldn't have merged it, because it wasn't
177179
ready yet, and I really need to undo _all_ of the merge"). So then you
178180
really should revert the merge, but when you want to re-do the merge, you
179181
now need to do it by reverting the revert.
182+
183+
ADDENDUM
184+
185+
Sometimes you have to rewrite one of a topic branch's commits *and* you can't
186+
change the topic's branching-off point. Consider the following situation:
187+
188+
P---o---o---M---x---x---W---x
189+
\ /
190+
A---B---C
191+
192+
where commit W reverted commit M because it turned out that commit B was wrong
193+
and needs to be rewritten, but you need the rewritten topic to still branch
194+
from commit P (perhaps P is a branching-off point for yet another branch, and
195+
you want be able to merge the topic into both branches).
196+
197+
The natural thing to do in this case is to checkout the A-B-C branch and use
198+
"rebase -i P" to change commit B. However this does not rewrite commit A,
199+
because "rebase -i" by default fast-forwards over any initial commits selected
200+
with the "pick" command. So you end up with this:
201+
202+
P---o---o---M---x---x---W---x
203+
\ /
204+
A---B---C <-- old branch
205+
\
206+
B'---C' <-- naively rewritten branch
207+
208+
To merge A-B'-C' into the mainline branch you would still have to first revert
209+
commit W in order to pick up the changes in A, but then it's likely that the
210+
changes in B' will conflict with the original B changes re-introduced by the
211+
reversion of W.
212+
213+
However, you can avoid these problems if you recreate the entire branch,
214+
including commit A:
215+
216+
A'---B'---C' <-- completely rewritten branch
217+
/
218+
P---o---o---M---x---x---W---x
219+
\ /
220+
A---B---C
221+
222+
You can merge A'-B'-C' into the mainline branch without worrying about first
223+
reverting W. Mainline's history would look like this:
224+
225+
A'---B'---C'------------------
226+
/ \
227+
P---o---o---M---x---x---W---x---M2
228+
\ /
229+
A---B---C
230+
231+
But if you don't actually need to change commit A, then you need some way to
232+
recreate it as a new commit with the same changes in it. The rebase commmand's
233+
--no-ff option provides a way to do this:
234+
235+
$ git rebase [-i] --no-ff P
236+
237+
The --no-ff option creates a new branch A'-B'-C' with all-new commits (all the
238+
SHA IDs will be different) even if in the interactive case you only actually
239+
modify commit B. You can then merge this new branch directly into the mainline
240+
branch and be sure you'll get all of the branch's changes.
241+
242+
You can also use --no-ff in cases where you just add extra commits to the topic
243+
to fix it up. Let's revisit the situation discussed at the start of this howto:
244+
245+
P---o---o---M---x---x---W---x
246+
\ /
247+
A---B---C----------------D---E <-- fixed-up topic branch
248+
249+
At this point, you can use --no-ff to recreate the topic branch:
250+
251+
$ git checkout E
252+
$ git rebase --no-ff P
253+
254+
yielding
255+
256+
A'---B'---C'------------D'---E' <-- recreated topic branch
257+
/
258+
P---o---o---M---x---x---W---x
259+
\ /
260+
A---B---C----------------D---E
261+
262+
You can merge the recreated branch into the mainline without reverting commit W,
263+
and mainline's history will look like this:
264+
265+
A'---B'---C'------------D'---E'
266+
/ \
267+
P---o---o---M---x---x---W---x---M2
268+
\ /
269+
A---B---C

git-rebase--interactive.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ v,verbose display a diffstat of what changed upstream
2020
onto= rebase onto given branch instead of upstream
2121
p,preserve-merges try to recreate merges instead of ignoring them
2222
s,strategy= use the given merge strategy
23+
no-ff cherry-pick all commits, even if unchanged
2324
m,merge always used (no-op)
2425
i,interactive always used (no-op)
2526
Actions:
@@ -110,6 +111,7 @@ VERBOSE=
110111
OK_TO_SKIP_PRE_REBASE=
111112
REBASE_ROOT=
112113
AUTOSQUASH=
114+
NEVER_FF=
113115

114116
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
115117
mark the corrected paths with 'git add <paths>', and
@@ -232,6 +234,7 @@ do_with_author () {
232234
pick_one () {
233235
ff=--ff
234236
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
237+
case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
235238
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
236239
test -d "$REWRITTEN" &&
237240
pick_one_preserving_merges "$@" && return
@@ -782,6 +785,9 @@ first and then run 'git rebase --continue' again."
782785
-i)
783786
# yeah, we know
784787
;;
788+
--no-ff)
789+
NEVER_FF=t
790+
;;
785791
--root)
786792
REBASE_ROOT=t
787793
;;
@@ -965,7 +971,7 @@ EOF
965971
has_action "$TODO" ||
966972
die_abort "Nothing to do"
967973

968-
test -d "$REWRITTEN" || skip_unnecessary_picks
974+
test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
969975

970976
git update-ref ORIG_HEAD $HEAD
971977
output git checkout $ONTO && do_rest

git-rebase.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Copyright (c) 2005 Junio C Hamano.
44
#
55

6-
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
6+
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
77
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
88
same name. When the --onto option is provided the new branch starts
99
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -353,7 +353,7 @@ do
353353
--root)
354354
rebase_root=t
355355
;;
356-
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
356+
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
357357
force_rebase=t
358358
;;
359359
--rerere-autoupdate|--no-rerere-autoupdate)

t/t3404-rebase-interactive.sh

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ set_fake_editor
2222
# | \
2323
# | F - G - H (branch1)
2424
# | \
25-
# \ I (branch2)
26-
# \
27-
# J - K - L - M (no-conflict-branch)
25+
# |\ I (branch2)
26+
# | \
27+
# | J - K - L - M (no-conflict-branch)
28+
# \
29+
# N - O - P (no-ff-branch)
2830
#
2931
# where A, B, D and G all touch file1, and one, two, three, four all
3032
# touch file "conflict".
33+
#
34+
# WARNING: Modifications to the initial repository can change the SHA ID used
35+
# in the expect2 file for the 'stop on conflicting pick' test.
36+
3137

3238
test_expect_success 'setup' '
3339
test_commit A file1 &&
@@ -48,6 +54,11 @@ test_expect_success 'setup' '
4854
done &&
4955
git checkout -b no-conflict-branch A &&
5056
for n in J K L M
57+
do
58+
test_commit $n file$n
59+
done &&
60+
git checkout -b no-ff-branch A &&
61+
for n in N O P
5162
do
5263
test_commit $n file$n
5364
done
@@ -113,7 +124,7 @@ cat > expect2 << EOF
113124
D
114125
=======
115126
G
116-
>>>>>>> 51047de... G
127+
>>>>>>> 5d18e54... G
117128
EOF
118129

119130
test_expect_success 'stop on conflicting pick' '
@@ -586,4 +597,21 @@ test_expect_success 'rebase while detaching HEAD' '
586597
test_must_fail git symbolic-ref HEAD
587598
'
588599

600+
test_tick # Ensure that the rebased commits get a different timestamp.
601+
test_expect_success 'always cherry-pick with --no-ff' '
602+
git checkout no-ff-branch &&
603+
git tag original-no-ff-branch &&
604+
git rebase -i --no-ff A &&
605+
touch empty &&
606+
for p in 0 1 2
607+
do
608+
test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
609+
git diff HEAD~$p original-no-ff-branch~$p > out &&
610+
test_cmp empty out
611+
done &&
612+
test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
613+
git diff HEAD~3 original-no-ff-branch~3 > out &&
614+
test_cmp empty out
615+
'
616+
589617
test_done

0 commit comments

Comments
 (0)