Wednesday, August 25, 2010

Using git-svn for cross-platform development

I do a lot of cross-platform development, mostly Windows and Linux. Being able to build and test on multiple platforms before pushing changes to other people is crucial in order to avoid build and test failures. Git is almost the ideal tool to do this, but since we are using Subversion at work (that won't change in the forseeable future), I've been trying out git-svn to see if it is up to the task.

A typical work-flow goes like this:
  1. Update local source tree with new changes from central repository
  2. ... hack hack ...
  3. Build and test on Linux
  4. Transfer local changes to a Windows machine
  5. Build and test on Windows machine
  6. Fix problem which only shows up on Windows
  7. Transfer changes back to Linux machine
  8. Commit all changes to central repository, confident that everything builds and runs on both Linux and Windows.
The tricky parts are the "transfer changes from machine A to machine B" without committing to the central repository. I've tried several solutions to this (source code on a network share, "manually" copying the sources (for various values of "manually")), but none of them really flew. I always ended up committing to the central repo anyway. Fortunately we have a fairly low-traffic repo, and I can usually fix broken things before breaking anyone elses build.

(For the rest of this post, I'm ignoring Subversion branches, even if git-svn does handle Subversion branches as well as could be expected.)

Now, using git-svn isn't completely unproblematic either. In an ideal world, I would like to be able to both push and pull to/from the central repo from both machines A and B as well as push/pull between them, but git-svn does not support that. The translation of git commits and their Subversion counterparts has to be done in a single place, or havoc will ensue. In practise, this means that we have a single repo where we run git-svn. This repo can be pulled from, but should not be pushed to (or git-svn will get confused when trying to pull new changes from the central Subversion repo), nor should we pull from any other repo into it. The exact reasons for this is the topic for another post.

How do we then get our changes back into the git-svn repo if we cannot push or pull into it? The solution is to use the command "git format-patch". This command creates a patch-set containing a set of changes. "git format-patch origin" will create a patch for all changes present in the local repo, which is not present in the "origin" remote, so if I on the Windows box pull from the Linux machine, I can do "git format-patch origin" to get all the local commits I've made which I need to apply to the repo on the Linux machine (the git-svn repo) and from there committed to the central Subversion repo.

Once the patches have been transferred to the Linux machine (this can be done in many ways, I just copy the .patch files over a network share), they can be applied using "git am *.patch". This will apply the patches and commit the change. Conflicts are resolved just as normal git conflicts.

The workflow then becomes:
  1. Bring in new changes from the central Subversion repo: git svn rebase
  2. ... hack hack ...
  3. Pull changes to Windows machine: git pull
  4. Build and test on Windows
  5. Fix eventual problems on Windows
  6. Generate patch set to bring back changes to Linux: git format-patch origin
  7. Copy patch files back to Linux
  8. Apply changes: git am *.patch
  9. Commit things back to Subversion: git svn dcommit
It is still a bit cumbersome, but the benefits are several:
  • I can produce commits which I have tested on both Windows and Linux.
  • I can use "git rebase -i" to edit/reorder the commits before finally pushing them to Subversion. This makes it easy to produce clear and concise commits.
  • I can use "git gui" to commit partial files. I can include a single added line from a file with hundreds of other changes in it. This also helps in produce commits which are easy to understand.
In short, git and git-svn allows me to write better code.