Skip to content

Commit b499549

Browse files
marcnarcgitster
authored andcommitted
Teach rebase the --no-ff option.
For git-rebase.sh, --no-ff is a synonym for --force-rebase. For git-rebase--interactive.sh, --no-ff cherry-picks all the commits in the rebased branch, instead of fast-forwarding over any unchanged commits. --no-ff offers an alternative way to deal with reverted merges. Instead of "reverting the revert" you can use "rebase --no-ff" to recreate the branch with entirely new commits (they're new because at the very least the committer time is different). This obviates the need to revert the reversion, as you can re-merge the new topic branch directly. Added an addendum to revert-a-faulty-merge.txt describing the situation and how to use --no-ff to handle it. Signed-off-by: Marc Branchaud <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 60dafdd commit b499549

File tree

5 files changed

+152
-10
lines changed

5 files changed

+152
-10
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 & 2 deletions
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:
@@ -103,6 +104,7 @@ VERBOSE=
103104
OK_TO_SKIP_PRE_REBASE=
104105
REBASE_ROOT=
105106
AUTOSQUASH=
107+
NEVER_FF=
106108

107109
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
108110
mark the corrected paths with 'git add <paths>', and
@@ -222,7 +224,7 @@ do_with_author () {
222224
}
223225

224226
pick_one () {
225-
no_ff=
227+
no_ff=$NEVER_FF
226228
case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
227229
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
228230
test -d "$REWRITTEN" &&
@@ -742,6 +744,9 @@ first and then run 'git rebase --continue' again."
742744
-i)
743745
# yeah, we know
744746
;;
747+
--no-ff)
748+
NEVER_FF=t
749+
;;
745750
--root)
746751
REBASE_ROOT=t
747752
;;
@@ -927,7 +932,7 @@ EOF
927932
has_action "$TODO" ||
928933
die_abort "Nothing to do"
929934

930-
test -d "$REWRITTEN" || skip_unnecessary_picks
935+
test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
931936

932937
git update-ref ORIG_HEAD $HEAD
933938
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>
@@ -347,7 +347,7 @@ do
347347
--root)
348348
rebase_root=t
349349
;;
350-
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
350+
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
351351
force_rebase=t
352352
;;
353353
--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' '
@@ -553,4 +564,21 @@ test_expect_success 'reword' '
553564
git show HEAD~2 | grep "C changed"
554565
'
555566

567+
test_tick # Ensure that the rebased commits get a different timestamp.
568+
test_expect_success 'always cherry-pick with --no-ff' '
569+
git checkout no-ff-branch &&
570+
git tag original-no-ff-branch &&
571+
git rebase -i --no-ff A &&
572+
touch empty &&
573+
for p in 0 1 2
574+
do
575+
test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
576+
git diff HEAD~$p original-no-ff-branch~$p > out &&
577+
test_cmp empty out
578+
done &&
579+
test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
580+
git diff HEAD~3 original-no-ff-branch~3 > out &&
581+
test_cmp empty out
582+
'
583+
556584
test_done

0 commit comments

Comments
 (0)