Friday, 12 August 2011

Pushing a git repository into an existing subversion repository

I worked on a project using git as the version control system. Eventally I needed to share it with other developers, who had an existing corporate Subversion repository. They didn't want to be bogged down in the minutae of learning a new version control system.

So I had to push the code in my git repo up to the svn repo.

There are two options:
  1. Take a baseline of the code and just comit that to svn, losing all version history. It's simple; it's quick; it works. Ten points for getting the job done. No points for elegance. And minus several thousand points for losing revision history.
  2. Serialise the development history in git and use that to re-vivify the history within Subversion. Many, many, points for doing the right thing. Minus quite a few for the faff it takes to work out how to do it.
Clearly, (2) is the way to go.

For your delight, this is how I finally managed to do it. Hopefully it'll help you avoid similar head scratching and Googling.

How to push a git repository into svn

Obviously, we'll want to invoke the git-svn command. The trick is how to arrange your git repo so that it's tracking the subversion repository correctly.

Step 1: Create the landing point in the svn repo

svn mkdir svn://DEST/repo/projectname/{trunk,branches,tags}

It's worth noting that in this example, I was happy to just clone the mainline of development, and ignore any git branches.

As you can infer, the corporate svn repo has many top-level directories that are all themselves "mini-repos". This is why I had to push the git history into an existing svn repo, rather than just create a new svn repo.

Step 2: Create a git svn clone tracking svn

git svn clone svn://DEST/repo/projectname/trunk dest

Now we have a git repo that tracks the destination svn landing point for the import operation.

Step 3: Track the git repo we want to import

cd dest
git remote add -f source /path/to/git/source/repo

Now, if you inspect the git history (for this I used the excellent GitX (L) ), you'll see a whole series of commits from the original git repo and, disconnected from this, the master HEAD plus a git-svn HEAD pointing to the original (single) svn commit we cloned.

Step 4: Rebase the original git repo onto git-svn

Here's where the secret magic lies. I seems like there are many ways to go from here. This was the only one I found to work. Of course, I tried many ways that failed. Once I found one that worked, I stopped trying. So if there's a better way I'd love to hear it, but this seems to work well.

git rebase --onto remotes/git-svn --root source/master

At this point, I realised that my git history wasn't strictly linear; I had worked on a few machines, so the history of trunk wove arond a bit.

This meant that what I had expected to be a straightforward operation (that's what you'd expect with a SVN hat on) required a few rebase fix-ups along the way:

gvim foo # fix merge conflict
git add foo
git rebase --continue
# ... rinse and repeat

These were required because branches in the source repo from work on different machines that got merged together to form the "source" trunk line of development didn't flatten into a rebase without a few tweaks.

In general, the conflicts were small and weren't hard to fix.

Step 5: Admire your work

git log

You should now see that the entire master development line of the source git repo has been replayed into the master of your working repo, stacked on top of the git-svn point.

Again, GitX (L) helped to visualise this.

Step 6: Push up to svn

Now that we've arranged everything above git-svn, it's a simple case of:

git svn dcommit

To push the changes up into svn.