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.


One thought on “Evil Merges in Git

  1. A canonical example of where “evil merge” happens in real life (and is a good thing) is to adjust for semantic conflicts. It almost always does not have much to do with textual conflicts.

    Imagine you and your friend start with the same codebase where a function f() takes no parameter, and has two existing callsites.

    You decide to update the function to take a parameter, and adjust both existing callsites to pass one argument to the function. Your friend in the meantime added a new callsite that calls f() still without an argument. Then you merge.

    It is very likely that you won’t see any textual conflict. Your friend added some code to block of lines you did not touch while you two were forked. However, the end result is now wrong. Your updated f() expects one parameter, while the new callsite your friend added does not pass any argument to it.

    In such a case, you would fix the new callsite your friend added to pass an appropriate argument and record that as part of your merge.

    Consider the line that has that new callsite you just fixed. It certainly did not exist in your version (it came from your friend’d code), but it is not exactly what your friend wrote, either (it did not pass any argument). That makes the merge an “evil merge”.

    With “git log -c/–cc”, such a line will show with double-plus in the multi-way patch output to show that “this did not exist in either parent”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s