[Update, 18/8/2008] If you’ve shared the branch with anyone else, or are pushing it to a clone of the repository, do not rebase, but use merge instead. From the man page:
When you rebase a branch, you are changing its history in a way that will cause problems for anyone who already has a copy of the branch in their repository and tries to pull updates from you. You should understand the implications of using git rebase on a repository that you share.
Do you get annoyed by seeing things like this in your git history?
commit a0b46a7c57e37f5dc43373ba9167ad2da32c1ec5 Merge: c2d8046... 73e0e15... Author: Fred BloggsDate: Tue Jun 17 17:30:49 2008 +0100 Merge branch 'master' into new_feature commit c2d8046c038d47940944e5b343d281b1d0c4d2b3 Author: Fred Bloggs Date: Tue Jun 17 17:30:43 2008 +0100 Added cool new feature
This happens when you use merge instead of rebase to keep a development branch up-to-date with master. Let’s watch what happens in each case.
The wrong way (merge)
You’re working on a cool new feature in your new_feature branch. You’ve committed two changes, and in the meantime there have been three other changes on master. Here’s the branch visualisation from gitk --all
:
You use merge to pull in those other three changes to your branch:
$ git merge master Merge made by recursive. file_1 | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
Now what this has actually done is added the changes from master as a new commit in new_feature. You can see this in the history:
myproj(new_feature) $ git log -1 commit cbc97a909641d3c325c6023a2459e556e62182e6 Merge: 024dc64... 0a71c4c... Author: Kerry BuckleyDate: Wed Jun 18 18:52:48 2008 +0100 Merge branch 'master' into new_feature
And also in the gitk
graph:
Now let’s say your new feature is all finished, so you merge it into master:
myproj(new_feature) $ git checkout master Switched to branch "master" myproj(master) $ git merge new_feature Updating 0a71c4c..cbc97a9 Fast forward file_3 | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
Let’s see what that’s done to the history:
myproj(master) $ git log commit cbc97a909641d3c325c6023a2459e556e62182e6 Merge: 024dc64... 0a71c4c... Author: Kerry BuckleyDate: Wed Jun 18 18:52:48 2008 +0100 Merge branch 'master' into new_feature commit 0a71c4c90aee5eeb60d15f199c4f8151756a8ae8 Author: Kerry Buckley Date: Wed Jun 18 18:48:19 2008 +0100 Third change in master commit 9970c0b72e7741804fc07bba50450b3d512e5572 Author: Kerry Buckley Date: Wed Jun 18 18:48:10 2008 +0100 Second change in master commit c44af4dee449082adf6741540d2f9e70968cf41e Author: Kerry Buckley Date: Wed Jun 18 18:46:33 2008 +0100 First change in master commit 024dc64022932a5a7b56c4fd7c7cf4a59d72e825 Author: Kerry Buckley Date: Wed Jun 18 18:45:15 2008 +0100 New feature finished commit eb4f05fb8ef8b93cf639b1e06528ef075f19f323 Author: Kerry Buckley Date: Wed Jun 18 18:45:01 2008 +0100 New feature partly done commit b4ffa1d35f808cc38e5f74fb2592224dd6f0e027 Author: Kerry Buckley Date: Wed Jun 18 18:44:02 2008 +0100 Last commit before creating branch
Or graphically:
The problem here is that the merge has applied all of the commits which were on new_feature but not on master, including the merge commit. That’s just ugly.
The right way (rebase)
Now, from exactly the same point, we’ll use rebase instead:
myproj(new_feature) $ git rebase master First, rewinding head to replay your work on top of it... HEAD is now at a644c41 Third change in master Applying New feature partly done Applying New feature finished
Basically, as it says, this has rewound all your changes since the new_feature branch diverged from master, moved the branch point up to the tip of master, then replayed your changes on top. The effect of this is as if you had created the branch from the current latest master, so no separate merge commit is required. Again, gitk --all
can confirm this visually:
Now merge the changes into master exactly as before:
myproj(new_feature) $ git checkout master Switched to branch "master" myproj(master) $ git merge new_feature Updating a644c41..d7d2233 Fast forward file_3 | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
No merge commit in the history this time, and a nice simple graph:
So there you have it. No excuse for polluting your history with merges any more!
[tags]git, merge, rebase, branch[/tags]
5 replies on “Avoiding Merge Commits in Git”
I’m very wary of rebase – though that’s probably due to ignorance.
As discussed on my blog, I’ve had some issues with using rebase. I can’t remember what it was exactly, but ended up using merge – and never had a problem since.
Maybe it’s time for another Kerry Teaches Git session at Osmosoft Towers!? (Worst case, I’ll wait for the next BarCamp… )
After an enlightening (and patient) explanation from Kerry, I’m starting to see the error of my ways:
One of my major concerns was that I wanted to preserve my branches’ detailed commit messages.
However, I’ve realized that’s nonsense; if the commits in the master are less meaningful than more fine-grained ones in the respective branch, then you’re simply Doing It Wrong…
Nevertheless, it’s still a bit strange that I can’t just keep merging stuff into the master without updating (whether with merge or rebase) the respective branch first:
git checkout MyBranch
git commit -a -m “foo”
git commit -a -m “bar”
git checkout master
git merge –squash MyBranch
git commit -a -m “foobar”
git svn dcommit
git checkout MyBranch
git [merge|rebase] master
GOTO #2
Kerry, thanks for the detailed exploration. It’s probably my newbieness with git, but I like having the merges in my history – it’s a good reminder of what people have been doing…)
[…] Rebase vs Merge in Git 2. Avoid Merge commit in Git, this one shows very detail about the difference on these two options, and it is the exact problem […]
Hi,
The purposes of git merge and git rebase are different.
We use the following to decide merge vs rebase:
If we’re pulling from remote, we do rebase
If we’re merging locally we do rebase only if the branch is ahead of remote parent
And, sometimes we want to know there were merges so we do git merge –no-ff to always force merge commit. This is very usefull in production branches as we know where to revert in case things goes wrong (git tag does the same, I know)