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
This commit is contained in:
commit
9234b00372
@ -274,9 +274,16 @@ which makes little sense.
|
|||||||
-f::
|
-f::
|
||||||
--force-rebase::
|
--force-rebase::
|
||||||
Force the rebase even if the current branch is a descendant
|
Force the rebase even if the current branch is a descendant
|
||||||
of the commit you are rebasing onto. Normally the command will
|
of the commit you are rebasing onto. Normally non-interactive rebase will
|
||||||
exit with the message "Current branch is up to date" in such a
|
exit with the message "Current branch is up to date" in such a
|
||||||
situation.
|
situation.
|
||||||
|
Incompatible with the --interactive option.
|
||||||
|
+
|
||||||
|
You may find this (or --no-ff with an interactive rebase) helpful after
|
||||||
|
reverting a topic branch merge, as this option recreates the topic branch with
|
||||||
|
fresh commits so it can be remerged successfully without needing to "revert
|
||||||
|
the reversion" (see the
|
||||||
|
link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
|
||||||
|
|
||||||
--ignore-whitespace::
|
--ignore-whitespace::
|
||||||
--whitespace=<option>::
|
--whitespace=<option>::
|
||||||
@ -316,7 +323,19 @@ which makes little sense.
|
|||||||
commit to be modified, and change the action of the moved
|
commit to be modified, and change the action of the moved
|
||||||
commit from `pick` to `squash` (or `fixup`).
|
commit from `pick` to `squash` (or `fixup`).
|
||||||
+
|
+
|
||||||
This option is only valid when '--interactive' option is used.
|
This option is only valid when the '--interactive' option is used.
|
||||||
|
|
||||||
|
--no-ff::
|
||||||
|
With --interactive, cherry-pick all rebased commits instead of
|
||||||
|
fast-forwarding over the unchanged ones. This ensures that the
|
||||||
|
entire history of the rebased branch is composed of new commits.
|
||||||
|
+
|
||||||
|
Without --interactive, this is a synonym for --force-rebase.
|
||||||
|
+
|
||||||
|
You may find this helpful after reverting a topic branch merge, as this option
|
||||||
|
recreates the topic branch with fresh commits so it can be remerged
|
||||||
|
successfully without needing to "revert the reversion" (see the
|
||||||
|
link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
|
||||||
|
|
||||||
include::merge-strategies.txt[]
|
include::merge-strategies.txt[]
|
||||||
|
|
||||||
|
@ -142,6 +142,8 @@ different resolution strategies:
|
|||||||
revert of a merge was rebuilt from scratch (i.e. rebasing and fixing,
|
revert of a merge was rebuilt from scratch (i.e. rebasing and fixing,
|
||||||
as you seem to have interpreted), then re-merging the result without
|
as you seem to have interpreted), then re-merging the result without
|
||||||
doing anything else fancy would be the right thing to do.
|
doing anything else fancy would be the right thing to do.
|
||||||
|
(See the ADDENDUM below for how to rebuild a branch from scratch
|
||||||
|
without changing its original branching-off point.)
|
||||||
|
|
||||||
However, there are things to keep in mind when reverting a merge (and
|
However, there are things to keep in mind when reverting a merge (and
|
||||||
reverting such a revert).
|
reverting such a revert).
|
||||||
@ -177,3 +179,91 @@ the answer is: "oops, I really shouldn't have merged it, because it wasn't
|
|||||||
ready yet, and I really need to undo _all_ of the merge"). So then you
|
ready yet, and I really need to undo _all_ of the merge"). So then you
|
||||||
really should revert the merge, but when you want to re-do the merge, you
|
really should revert the merge, but when you want to re-do the merge, you
|
||||||
now need to do it by reverting the revert.
|
now need to do it by reverting the revert.
|
||||||
|
|
||||||
|
ADDENDUM
|
||||||
|
|
||||||
|
Sometimes you have to rewrite one of a topic branch's commits *and* you can't
|
||||||
|
change the topic's branching-off point. Consider the following situation:
|
||||||
|
|
||||||
|
P---o---o---M---x---x---W---x
|
||||||
|
\ /
|
||||||
|
A---B---C
|
||||||
|
|
||||||
|
where commit W reverted commit M because it turned out that commit B was wrong
|
||||||
|
and needs to be rewritten, but you need the rewritten topic to still branch
|
||||||
|
from commit P (perhaps P is a branching-off point for yet another branch, and
|
||||||
|
you want be able to merge the topic into both branches).
|
||||||
|
|
||||||
|
The natural thing to do in this case is to checkout the A-B-C branch and use
|
||||||
|
"rebase -i P" to change commit B. However this does not rewrite commit A,
|
||||||
|
because "rebase -i" by default fast-forwards over any initial commits selected
|
||||||
|
with the "pick" command. So you end up with this:
|
||||||
|
|
||||||
|
P---o---o---M---x---x---W---x
|
||||||
|
\ /
|
||||||
|
A---B---C <-- old branch
|
||||||
|
\
|
||||||
|
B'---C' <-- naively rewritten branch
|
||||||
|
|
||||||
|
To merge A-B'-C' into the mainline branch you would still have to first revert
|
||||||
|
commit W in order to pick up the changes in A, but then it's likely that the
|
||||||
|
changes in B' will conflict with the original B changes re-introduced by the
|
||||||
|
reversion of W.
|
||||||
|
|
||||||
|
However, you can avoid these problems if you recreate the entire branch,
|
||||||
|
including commit A:
|
||||||
|
|
||||||
|
A'---B'---C' <-- completely rewritten branch
|
||||||
|
/
|
||||||
|
P---o---o---M---x---x---W---x
|
||||||
|
\ /
|
||||||
|
A---B---C
|
||||||
|
|
||||||
|
You can merge A'-B'-C' into the mainline branch without worrying about first
|
||||||
|
reverting W. Mainline's history would look like this:
|
||||||
|
|
||||||
|
A'---B'---C'------------------
|
||||||
|
/ \
|
||||||
|
P---o---o---M---x---x---W---x---M2
|
||||||
|
\ /
|
||||||
|
A---B---C
|
||||||
|
|
||||||
|
But if you don't actually need to change commit A, then you need some way to
|
||||||
|
recreate it as a new commit with the same changes in it. The rebase commmand's
|
||||||
|
--no-ff option provides a way to do this:
|
||||||
|
|
||||||
|
$ git rebase [-i] --no-ff P
|
||||||
|
|
||||||
|
The --no-ff option creates a new branch A'-B'-C' with all-new commits (all the
|
||||||
|
SHA IDs will be different) even if in the interactive case you only actually
|
||||||
|
modify commit B. You can then merge this new branch directly into the mainline
|
||||||
|
branch and be sure you'll get all of the branch's changes.
|
||||||
|
|
||||||
|
You can also use --no-ff in cases where you just add extra commits to the topic
|
||||||
|
to fix it up. Let's revisit the situation discussed at the start of this howto:
|
||||||
|
|
||||||
|
P---o---o---M---x---x---W---x
|
||||||
|
\ /
|
||||||
|
A---B---C----------------D---E <-- fixed-up topic branch
|
||||||
|
|
||||||
|
At this point, you can use --no-ff to recreate the topic branch:
|
||||||
|
|
||||||
|
$ git checkout E
|
||||||
|
$ git rebase --no-ff P
|
||||||
|
|
||||||
|
yielding
|
||||||
|
|
||||||
|
A'---B'---C'------------D'---E' <-- recreated topic branch
|
||||||
|
/
|
||||||
|
P---o---o---M---x---x---W---x
|
||||||
|
\ /
|
||||||
|
A---B---C----------------D---E
|
||||||
|
|
||||||
|
You can merge the recreated branch into the mainline without reverting commit W,
|
||||||
|
and mainline's history will look like this:
|
||||||
|
|
||||||
|
A'---B'---C'------------D'---E'
|
||||||
|
/ \
|
||||||
|
P---o---o---M---x---x---W---x---M2
|
||||||
|
\ /
|
||||||
|
A---B---C
|
||||||
|
@ -20,6 +20,7 @@ v,verbose display a diffstat of what changed upstream
|
|||||||
onto= rebase onto given branch instead of upstream
|
onto= rebase onto given branch instead of upstream
|
||||||
p,preserve-merges try to recreate merges instead of ignoring them
|
p,preserve-merges try to recreate merges instead of ignoring them
|
||||||
s,strategy= use the given merge strategy
|
s,strategy= use the given merge strategy
|
||||||
|
no-ff cherry-pick all commits, even if unchanged
|
||||||
m,merge always used (no-op)
|
m,merge always used (no-op)
|
||||||
i,interactive always used (no-op)
|
i,interactive always used (no-op)
|
||||||
Actions:
|
Actions:
|
||||||
@ -110,6 +111,7 @@ VERBOSE=
|
|||||||
OK_TO_SKIP_PRE_REBASE=
|
OK_TO_SKIP_PRE_REBASE=
|
||||||
REBASE_ROOT=
|
REBASE_ROOT=
|
||||||
AUTOSQUASH=
|
AUTOSQUASH=
|
||||||
|
NEVER_FF=
|
||||||
|
|
||||||
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
|
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
|
||||||
mark the corrected paths with 'git add <paths>', and
|
mark the corrected paths with 'git add <paths>', and
|
||||||
@ -232,6 +234,7 @@ do_with_author () {
|
|||||||
pick_one () {
|
pick_one () {
|
||||||
ff=--ff
|
ff=--ff
|
||||||
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
|
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
|
||||||
|
case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
|
||||||
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
||||||
test -d "$REWRITTEN" &&
|
test -d "$REWRITTEN" &&
|
||||||
pick_one_preserving_merges "$@" && return
|
pick_one_preserving_merges "$@" && return
|
||||||
@ -782,6 +785,9 @@ first and then run 'git rebase --continue' again."
|
|||||||
-i)
|
-i)
|
||||||
# yeah, we know
|
# yeah, we know
|
||||||
;;
|
;;
|
||||||
|
--no-ff)
|
||||||
|
NEVER_FF=t
|
||||||
|
;;
|
||||||
--root)
|
--root)
|
||||||
REBASE_ROOT=t
|
REBASE_ROOT=t
|
||||||
;;
|
;;
|
||||||
@ -965,7 +971,7 @@ EOF
|
|||||||
has_action "$TODO" ||
|
has_action "$TODO" ||
|
||||||
die_abort "Nothing to do"
|
die_abort "Nothing to do"
|
||||||
|
|
||||||
test -d "$REWRITTEN" || skip_unnecessary_picks
|
test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
|
||||||
|
|
||||||
git update-ref ORIG_HEAD $HEAD
|
git update-ref ORIG_HEAD $HEAD
|
||||||
output git checkout $ONTO && do_rest
|
output git checkout $ONTO && do_rest
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# Copyright (c) 2005 Junio C Hamano.
|
# Copyright (c) 2005 Junio C Hamano.
|
||||||
#
|
#
|
||||||
|
|
||||||
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
|
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
|
||||||
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
|
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
|
||||||
same name. When the --onto option is provided the new branch starts
|
same name. When the --onto option is provided the new branch starts
|
||||||
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
|
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
|
||||||
@ -353,7 +353,7 @@ do
|
|||||||
--root)
|
--root)
|
||||||
rebase_root=t
|
rebase_root=t
|
||||||
;;
|
;;
|
||||||
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
|
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
|
||||||
force_rebase=t
|
force_rebase=t
|
||||||
;;
|
;;
|
||||||
--rerere-autoupdate|--no-rerere-autoupdate)
|
--rerere-autoupdate|--no-rerere-autoupdate)
|
||||||
|
@ -22,12 +22,18 @@ set_fake_editor
|
|||||||
# | \
|
# | \
|
||||||
# | F - G - H (branch1)
|
# | F - G - H (branch1)
|
||||||
# | \
|
# | \
|
||||||
# \ I (branch2)
|
# |\ I (branch2)
|
||||||
# \
|
# | \
|
||||||
# J - K - L - M (no-conflict-branch)
|
# | J - K - L - M (no-conflict-branch)
|
||||||
|
# \
|
||||||
|
# N - O - P (no-ff-branch)
|
||||||
#
|
#
|
||||||
# where A, B, D and G all touch file1, and one, two, three, four all
|
# where A, B, D and G all touch file1, and one, two, three, four all
|
||||||
# touch file "conflict".
|
# touch file "conflict".
|
||||||
|
#
|
||||||
|
# WARNING: Modifications to the initial repository can change the SHA ID used
|
||||||
|
# in the expect2 file for the 'stop on conflicting pick' test.
|
||||||
|
|
||||||
|
|
||||||
test_expect_success 'setup' '
|
test_expect_success 'setup' '
|
||||||
test_commit A file1 &&
|
test_commit A file1 &&
|
||||||
@ -48,6 +54,11 @@ test_expect_success 'setup' '
|
|||||||
done &&
|
done &&
|
||||||
git checkout -b no-conflict-branch A &&
|
git checkout -b no-conflict-branch A &&
|
||||||
for n in J K L M
|
for n in J K L M
|
||||||
|
do
|
||||||
|
test_commit $n file$n
|
||||||
|
done &&
|
||||||
|
git checkout -b no-ff-branch A &&
|
||||||
|
for n in N O P
|
||||||
do
|
do
|
||||||
test_commit $n file$n
|
test_commit $n file$n
|
||||||
done
|
done
|
||||||
@ -113,7 +124,7 @@ cat > expect2 << EOF
|
|||||||
D
|
D
|
||||||
=======
|
=======
|
||||||
G
|
G
|
||||||
>>>>>>> 51047de... G
|
>>>>>>> 5d18e54... G
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'stop on conflicting pick' '
|
test_expect_success 'stop on conflicting pick' '
|
||||||
@ -586,4 +597,21 @@ test_expect_success 'rebase while detaching HEAD' '
|
|||||||
test_must_fail git symbolic-ref HEAD
|
test_must_fail git symbolic-ref HEAD
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_tick # Ensure that the rebased commits get a different timestamp.
|
||||||
|
test_expect_success 'always cherry-pick with --no-ff' '
|
||||||
|
git checkout no-ff-branch &&
|
||||||
|
git tag original-no-ff-branch &&
|
||||||
|
git rebase -i --no-ff A &&
|
||||||
|
touch empty &&
|
||||||
|
for p in 0 1 2
|
||||||
|
do
|
||||||
|
test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
|
||||||
|
git diff HEAD~$p original-no-ff-branch~$p > out &&
|
||||||
|
test_cmp empty out
|
||||||
|
done &&
|
||||||
|
test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
|
||||||
|
git diff HEAD~3 original-no-ff-branch~3 > out &&
|
||||||
|
test_cmp empty out
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user