The man page git-rebase(1) says:
-m
--merge
Use merging strategies to rebase. [...]
But of course one can also run into "merge conflicts" without using the --merge
option. So also in that case there must be any "merge strategy" to handle these conflicts.
What difference makes the --merge
option to a rebase.
It seems to be something rather fundamental: For a rebase --merge
, Git stores its working files in a folder named $GIT_DIR/rebase-merge
(as it does for interactive rebases). If the --merge
option is not used (and the rebase is non-interactive) that folder is named $GIT_DIR/rebase-apply
.
What is git rebase? From a content perspective, rebasing is changing the base of your branch from one commit to another making it appear as if you'd created your branch from a different commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base.
For individuals, rebasing makes a lot of sense. If you want to see the history completely same as it happened, you should use merge. Merge preserves history whereas rebase rewrites it . Rebasing is better to streamline a complex history, you are able to change the commit history by interactive rebase.
In one sentence, what -m
or --merge
does for git rebase
is to make sure that rebase uses git cherry-pick
internally.
The -m
flag to force cherry-pick is often, but not always, redundant. In particular, any interactive rebase always uses cherry-pick anyway. As joanis noted in a comment, specifying any -s
or -X
options also force the use of cherry-pick. So does -k
, as noted below.
Rebase has a long history in Git: the first rebase operations were done by formatting each commit-to-be-rebased into a patch, then applying the patch to some other commit. That is, originally, git rebase
was mostly just:
branch=$(git symbolic-ref --short HEAD)
target=$(git rev-parse ${onto:-$upstream})
git format-patch $upstream..HEAD > $temp_file
git checkout $target
git am -3 $temp_file
git checkout -B $branch HEAD
(except for argument handling, all the error checking, and the fact that the git am
can stop with an error, requiring hand-fixing and git rebase --continue
; also, the above scripting is my reduced-for-readability version and probably does not resemble the original script much).
This kind of rebase handles most cases fairly well. The most common case that it doesn't handle well involves rebasing across some file renames. It also cannot copy an "empty" commit—one whose patch is empty, that is—as git format-patch
is not allowed to omit the patch part.
These empty commits are normally omitted by git rebase
even when using -m
; you must add -k
to preserve them. To preserve them, git rebase
must switch to the cherry-pick variant, if it has not already done so.
To pass -s
or -X
arguments, rebase must invoke git cherry-pick
rather than git am
, so any of those flags also require the cherry-pick variant.
Using git format-patch
never does any rename detection. Hence, if the stream of commits you're copying should all have rename detection applied with respect to HEAD
, the -m
flag is very important. For a concrete example, consider this series of commits:
B--C--D <-- topic
/
...--o--A--E--F--G <-- mainline
Suppose that the difference from A
to B
, B
to C
, and C
to D
is all handled within a file named lib-foo.ext
. But in commit F
, this file is renamed to be lib/foo.ext
instead. A git format-patch
of A..D
will show changes to be made to file lib-foo.ext
, none of which will apply correctly to commit G
as there is no lib-foo.ext
file. The rebase as a whole will fail.
A git cherry-pick
of commit B
when HEAD
identifies commit G
, however, will find the rename and apply the A
-vs-B
changes to the version of lib/foo.ext
in commit G
:
B--C--D <-- topic
/
...--o--A--E--F--G <-- mainline
\
B' <-- HEAD [detached]
The next cherry-pick, of C
while HEAD
identifies B'
, will discover that the B
-to-C
change to libfoo.ext
should be applied to the renamed lib/foo.ext
, and the last cherry-pick of D
will do the same, so that the rebase will succeed.
The rename detection code is slow, so a rebase that has no renames to do, and no "empty" commits to keep, can run much faster when run via the git format-patch | git am
system. That's about the only way in which the original method is better than the cherry-pick variant: it's faster in constrained cases. (However, the speed improvement only occurs when there are lots of rename candidates, but either none of them are actual renames, or none of them matter.)
(Side note: the -3
argument, or --3way
to use the longer spelling, tells git am
to pass that flag on to each git apply
, where the apply will attempt to do a three-way merge if needed, using the blob hashes in the index
line in the diff. Under some conditions, it seems like this might suffice to handle renamed files—in particular if the blob hash exactly matches. The cherry-pick method does full rename detection, which handles inexact matches; -3
cannot do that. See also What is the difference between git cherry-pick and git format-patch | git am?, as Jürgen noted.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With