How Git outshines Subversion at Merging

I’ve recently started getting my head around Git having been a Subversion (sadly even CVS) user all along. While I liked the fact that it was distributed and way faster than Subversion at most operations, I couldn’t understand why many claimed it was better at merging than Subversion (1.5 and above). Even the “Branch Handling” section in Git’s wiki talks only about better history tracking but not about how Git is better at merging the content themselves.

While correct history tracking is important, merging without breaking the code is an absolute must. So I wanted to see if there was a place where Subversion would silently break the code during a merge but Git would not. This might not be new to long time Git users but I hope this would help many Subversion users take another look at Git. Without further ado, lets see how Git manages merges better.

The Workflow

You initially start off with a file named Test.java in both SVN and Git repositories with initial content as below.

public class Test {
    public static void main(String[] args) {
        System.out.println("Wrong message!");
    }
}

You then create a branch named “refactor” and within this branch rename the class to NicerName (and consequently rename the file too) and commit to the branch.

public class NicerName {
    public static void main(String[] args) {
        System.out.println("Wrong message!");
    }
}

You switch back to the main branch (trunk/master) and fix a bug. In this case we change the message being printed to “Correct Message” and commit to the main branch.

public class Test {
    public static void main(String[] args) {
        System.out.println("Correct message!");
    }
}

Finally, we merge the changes from the refactor branch into the main branch.

The Commands

SVN would do it with the following commands.

svn co $MY_SVN_ROOT/trunk svn_sample
cd svn_sample
svn cp . $MY_SVN_ROOT/branches/refactor
svn switch $MY_SVN_ROOT/branches/refactor
# Rename class
svn mv Test.java NicerName.java
svn ci -m "Renamed class."
svn switch $MY_SVN_ROOT/trunk
#Change to correct message
svn ci -m "Fixed message."
svn up
svn merge --reintegrate $MY_SVN_ROOT/branches/refactor

Git would do the same with these commands.

git clone $MY_GIT_ORIGIN git_sample
cd git_sample
git checkout -b refactor
# Rename class
git mv Test.java NicerName.java
git commit -a -m "Renamed class."
git checkout master
#Change to correct message
git commit -a -m "Fixed message."
git merge refactor

The Result

Neither SVN nor Git complain about conflicts which is fine. However, after the merge, SVN leaves two files in the main branch. NicerName.java has the new name but doesn’t have the fix while Test.java has the fix but not the new name. Here is how it looks in SVN.

public class NicerName {
    public static void main(String[] args) {
        System.out.println("Wrong message!");
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println("Correct message!");
    }
}

Essentially your code is broken silently by SVN during the merge. On the other hand Git handles this very cleanly. It leaves only NicerName.java back including the fix to the message and removes Test.java, exactly as you’d expect.

public class NicerName {
    public static void main(String[] args) {
        System.out.println("Correct message!");
    }
}

Over all the branching and merging commands looked cleaner with Git too. This kind of excellent support for branching, refactoring and merging back alone should make everyone give Git a serious consideration. Even if Git is used in a centralized workflow just like Subversion, it can still provide huge benefits over Subversion. If you utilize its distribution features too, it just gets better!

22 Jan, 2010 git / svn