19 Jun 2008 Using Git for Local Version Control
As I work, I like to be able to do very regular checkins to our Subversion server (ensuring the tests pass beforehand, of course). This gives me an easy fallback position if I make some change that causes test breakages. Sometimes it’s just easier to rollback and start again than to try and figure out how I broke it. This is especially the case when refactoring.
We use Crucible for our code reviews, connected to our Subversion server. The problem is that when I want somebody to review a new feature or bug fix, the reviews might need to span a bunch of these fine-grained changesets. Crucible allows you to have multiple changesets in a review, but I don’t want people to have to review every little changeset – I want them to be able to just look at the difference between the start point and the end point.
To get around this, I’ve been using Git to track my local changes, a custom script to bundle my changes into a single changeset, then git-svn to commit them to my Subversion server when I am ready.
I’m not going to give you a tutorial on Git here, but here are the basic steps involved:
- Get Git and git-svn.
- Clone your remote SVN repository into a local Git repository. If your SVN repository has a lot of history, this may take a while – but this is a one-off event. Go get a coffee whilst you wait.
- Once it’s done, start coding. At this point it might be worth reading some of the introductory Git documentation to get an understanding of how it works. Commit your changes to your local Git repository as per normal Git usage.
- Periodically rebase to the remote SVN repository using
git svn rebase. This will ensure you’ve got the latest changes in the SVN repository.
- Repeat steps 3 and 4 until you’re happy with your changes. Create branches if you want, but don’t merge between them (we’ll discuss why later).
- When you’re ready to check-in to SVN, first use a custom bash script that was written by my colleague Tom Lee. Why do this? Well, to commit from Git to Subversion, you’d normally just use:
git svn dcommit
However, the problem with this is that’ll it commit a new changeset to SVN for each Git commit you’ve done. We don’t want this. Instead, we want to bundle everything up into a single commit first.
Update: As suggested by one of the commenters, a better way to do this is to use
git-svn rebase -iSee My git-svn workflow for further information.
The way to do is to create a temporary Git branch, and merge all of your changes into it. This will squash them into a single changeset, which you can then commit to SVN in one hit. Whilst you can do this manually if you want, Tom’s script does this for you automatically.
Put the script in the same directory as your other Git executables and make it executable too. You can then run it as follows:
git-prepare-svn-commit -m 'Added some new feature'
It’ll bundle everything up into a single changeset with the provided commit message. Note that this script has only been tested with Ubuntu – your mileage may vary.
- Now you can check it in:
git svn dcommit
and it’ll just commit a single bundled changeset to your Subversion repository.
Additional Benefits of using Git
Git has a number of features that are very appealing. One that stood out for me is the ability to very quickly and easily create branches locally and then instantly switch between them. Often I would have a number of branches going at the same time, each one for different code enhancements or defect fixes.
In theory I could probably do all of this using Subversion branches, but Git offered one additional advantage – I don’t require a network connection to use it. I regularly work with it offline, only having to find a network connection when I’m ready to upload my changes to Subversion. I also have the added bonus that I don’t end up with a bazillion branches hanging around on the Subversion server.
It’s worth noting that I’ve never merged between local Git branches that originated from the Subversion server – indeed, the git-svn documentation recommends against it (see the ‘Caveats’ section of the git-svn Manual Page). However, this wasn’t ever a problem for me; by committing finished work to Subversion, switching to a Git branch containing unfinished work and then rebasing against the Subversion repository, code could easily be transmitted between branches.
Drawbacks of using Git
I found Git to have a steep learning curve. In particular, it’s two-stage approach to commits took a little getting used to. This problem was exacerbated by the fact that I was using git-svn as well. The best example of this was the extreme bewilderment I experienced when I first encountered merge conflicts during a rebase against the Subversion repository.
The best piece of advice I can give you if you encounter such a conflict during a rebase is to do what Git tells you. Git will provide a number of ‘what you can do next’ instructions when it encounters a conflict that it needs you to resolve. Read these instructions carefully and follow them. I tried to short-cut them and ended up hopelessly tangled up.
Finally, there’s not much Git tool support out there at the moment, so you’re pretty much gonna be doing it all from the command-line.
Thanks to Tom Lee for breaking a trail to this solution – I knew what I wanted and thought Git might provide a solution, but Tom was the one who actually figured it out and put it all together. He also pulled me out of a few holes that I dug myself into during the learning process.