Evil Merges in Git

What makes Evil Merges in Git … Evil?

The Git Glossary defines an “Evil merge” as…

a merge that introduces changes that do not appear in any parent.

To me that sounds like any merge with a non-trivial conflict. The sort of conflict that needs hand editing, rather than simply taking yours or theirs for each hunk. That’s pretty common, so why is it evil? To be honest I’m just guessing, but one feature of git that slowed me down considerably when trying to find out who had removed some lines from the file is that git log pretends that merge commits don’t change any files. Try

git log --stat

on a repository with merges. It says merges never change any files, even though they really do. Adding the options -c, --cc or -m to git log suddenly shows merge stats for files which had to change in a merge. When you do a search with git pickaxe the behaviour is the same; the search won’t find anything in merge commits unless you use one of those options.

To me that sounds totally crazy, but perhaps it makes some sense if you’re interested in where changes were first introduced. In that case you’re interested in the initial commit which created the change, not the merge-commit which is a later artifact of the revision control process.

However it hides the contents of Evil Merges by default and to my mind that is Git rather than the Merge being Evil.


Conflicts in Git Rebasing

After using Git for a while I’m finding the most confusing aspect of it is rebasing. The git-rebase man page says “forward-port local commits to the updated upstream head”. Hmm. Thankfully James Bowes posted a solid explanation of the basic principles.

Which leaves me wondering about corner cases. I have local commits which I’ve submitted as patches upstream. Now I’ll be fetching my changes from upstream, one of which has an updated comment and the other has had minor edits applied. How does Git handle this? In a good way! For commits which are already upstream, with identical content, even with differing comments, rebasing removes the local commit. That’s exactly what you want. For example simply specify the upstream branch to rebase against:

git rebase origin/master

In cases where the commit contents have received minor edits the rebase will pause. Assuming you are happy to drop the local commit tell Git to do exactly that:

git rebase --skip

or if there’s any doubt tell Git to rollback to before the rebase started:

git rebase --abort