Avoid merge hell when committing from Git to a Subversion repository

Previously (I sound like an episode of desperate housewives!) we saw how we can easily commit an existing Git repository into Subversion. That was great and we can commit changes to our git repository and execute git svn dcommit all day long to send the changes up to Subversion. Now things get a little hairy when you throw a second developer into the mix who is committing to the same Subversion repository.

If you’ve seen the Merge conflict during commit: Your file or directory… error message when doing a git svn dcommit don’t panic yet. See here on how to resolve this. I’m not going to talk about how to fix this issue. Instead we will look at what the appropriate workflow should’ve been to begin with once we got our Git repository committed into Subversion.

If you don’t have a Git repository that is linked up with a Subversion location, here are the Git commands that will bring down a project in Subversion into a Git repository on your workstation:

$ mkdir myproject
$ cd myproject
$ git svn init --trunk=http://my.svnserver.com/repos/myproject/trunk \
--tags=http://my.svnserver.com/repos/myproject/tags \
--branches=http://my.svnserver.com/repos/myproject/branches \
--username <your svn username>
$ git svn fetch -r <starting subversion revision # to fetch from>

Note: I’ve had much better luck using a more recent revision number in Subversion than trying to checkout the entire project history.

Now that we have our repositories laid out lets get to work. The key point to remember is we never commit to the master branch in our Git repository. Instead we use master to simply keep up with the project’s trunk in Subversion. So the only git commands that get executed on the master are git svn fetch followed by git rebase trunk. We’ll go into these in more detail below.

We have a bunch of changes to make in the project. Here’s what our workflow is going to look like:

Step 1: Create a separate branch (say local-dev) from master where we make our changes.
$ git checkout master # make sure you are on the master branch
$ git checkout -b local-dev # the new branch where we make all our changes

Step 2: Proceed to make changes on the local-dev branch
$ git add FileThatChanged
$ git commit -m 'your commit message'

Step 3: Now stash away any changes in our working copy that we haven’t committed into git and that we don’t want pushed up to Subversion
$ git stash

Step 4: Update our master branch with any changes that have happened on Subversion trunk since the last time we fetched from Subversion.
$ git checkout master #switch back to the master branch since that's what we use to track Subversion trunk
$ git svn fetch # pull down the changes from Subversion into the remotes/trunk
$ git rebase trunk # apply any changes from Subversion onto the master branch
$ git log # to view the changes we got from Subversion

Note: See this post for a sweet tip on customizing your git log messages to be more easily readable.

Step 5: Update local-dev with the changes from master
$ git checkout local-dev # switch to our local development branch
$ git rebase master # apply all the changes that were brought down from Subversion trunk into master into our local-dev resolving any conflicts
$ git log # notice how the Git rebase command moved our commit to be last commit on this branch

Step 6: Push our commit to Subversion trunk
$ git svn dcommit

Step 7: Bring back our changes that were stashed
$ git stash pop

Rinse and repeat. It probably reads like a lot more work that it really is especially once you’ve gotten the hang of the workflow. But this should avoid the merge hell …or impasse rather when syncing with Subversion. Now, what are you waiting for. Go Git that project from Subversion!