Reputation: 12846
I have the follwing situation:
$ git --version
git version 2.7.3.windows.1
$ git log --graph --oneline
* 83e3254 version 1.1
* 34188af merge of feature into master
|\
| * 784ba31 awesome change
|/
* 6eec273 added file1
* 84d80a5 added version file
To reproduce this in a new directory
rm -rf .git
git init
echo version 1.0 > version.txt
git add version.txt
git commit -m "added version file"
echo file1 > file1
git add file1
git commit -m "added file1"
git checkout -b feature
echo awesome change >> file1 && git commit -am "awesome change"
git checkout master
git merge --no-ff --no-commit feature
echo "small fixup" >> file1
git commit -am "merge of feature into master"
echo version 1.1 > version.txt
git commit -am "version 1.1"
Now I saw this feature was meant for version 1.1. So I did this:
git rebase --preserve-merges -i master~3
with this as git-rebase-todo
pick 6eec273 added file1
pick 83e3254 version 1.1
pick 784ba31 awesome change
pick 34188af merge of feature into master
and got this:
$ git log --graph --oneline
* 34188af merge of feature into master
|\
| * 784ba31 awesome change
|/
* 6eec273 added file1
* 84d80a5 added version file
what happened to 83e3254? Have I missed something? Should I use 34188af in the todofile when I need to preserve merges?
Upvotes: 4
Views: 1294
Reputation: 487725
Melebius' answer is correct (and upvoted); it has to do with the way interactive rebase tries to replay the commits as a series of cherry-picks. Since a merge is not actually a cherry-pick, the preserve-merge mode uses a secret side channel1 to figure out how to re-perform merges, and this side channel depends on the commit ordering.
There is, however, another problem with re-performing a merge. The merge is redone by running git merge
. Here is your original merge:
git checkout master
git merge --no-ff --no-commit feature
echo "small fixup" >> file1 # DANGER WILL ROBINSON
git commit -am "merge of feature into master"
(note my added remark). This "small fixup" is not part of the two inputs: it was added manually via the --no-commit
feature and, well, manual manipulation. This is sometimes called an "evil merge":
When git rebase -p
"replays" the merge, it does so without --no-commit
, and then automatically moves on to the next linearized commit. So any changes deliberately introduced like this will be lost, even if you preserve the commit ordering and hence get an otherwise correct rebased history.
This is documented—see the quoted text, which says:
Merge conflict resolutions or manual amendments to merge commits are not preserved.
—but in my opinion this sentence needs to be in bold blinking 72-point font or something. :-)
1It's not really very secret at all: simply view the interactive rebase code. Using your file viewer or editor, look at $(git --exec-path)/git-rebase--interactive
. Search for the string "preserv" (as in preserve or preserving) and observe the function pick_one_preserving_merges
and the code in pick_one
that calls it if a directory named $rewritten
exists. The usage of the $rewritten
directory is rather complex, though (it applies to both merge-preserving and non-merge-preserving interactive rebases and relies on the fact that without the --preserve
or -p
option, the initial "todo" list contains no actual merges and therefore simply flattens them away).
Upvotes: 5
Reputation: 6695
This is probably related with the documented imperfectness of the rebase mechanism. From the git rebase
documentation:
-p --preserve-merges
Recreate merge commits instead of flattening the history by replaying commits a merge commit introduces. Merge conflict resolutions or manual amendments to merge commits are not preserved.
This uses the
--interactive
machinery internally, but combining it with the--interactive
option explicitly is generally not a good idea unless you know what you are doing (see BUGS below).
The -i
and --interactive
options are synonymous.
Let’s see the BUGS section:
The todo list presented by
--preserve-merges --interactive
does not represent the topology of the revision graph. Editing commits and rewording their commit messages should work fine, but attempts to reorder commits tend to produce counterintuitive results.For example, an attempt to rearrange
1 --- 2 --- 3 --- 4 --- 5
to
1 --- 2 --- 4 --- 3 --- 5
by moving the "pick 4" line will result in the following history:
3 / 1 --- 2 --- 4 --- 5
Upvotes: 5