May 24, 2010
Easier Partial "DCommitting"
I've been making extensive use of Git-SVN recently, because I vastly prefer the user interface of Git to that of SVN, yet am collaborating on a project which uses SVN. My workflow consists of keeping a set of commits on top of the commits from SVN, which do things like add .gitignore files (hint: use git svn create-ignore to make these automatically), or contain code that I'm not ready to show other people just yet, or don't ever want to release.
This isn't the most advanced use of git-svn possible, but it's quite neat, simple and difficult to make mistakes with. When I described the method on IRC to someone who was asking about exactly this situation, it sounded like my method was of interest. I promised to do a short write-up, and here it is.
My workflow consists of three simple(ish) aliases. The first one is the simplest, for updating to the latest SVN trunk:
git stash && git svn rebase && git stash pop
This just stashes any local changes to the working tree, then rebases the unpublished, "floating" commits on top of the latest commits from SVN.
The second is used when I have one or more commits to add to the SVN repository. Having previously created a branch "for-commit" (it doesn't matter where it's based initially), I open "gitk" and make sure it's up to date. It probably looks something like this:
Then I invoke the second alias, which is:
git stash && git checkout for-commit && git reset --hard remotes/git-svn
This stashes local changes, checks out the "for-commit" branch and resets it to the most recent known head of the Subversion trunk. Then I refresh my gitk window, which then looks like this:
Nothing too exciting has happened, but we're now on the "for-commit" branch which points in a sensible place. I right-click* each commit that's to be committed to Subversion, and cherry-pick them onto the "for-commit" branch:
So, now the "for-commit" branch has diverged from "master" because the commits have been re-written by the cherry-pick operation. Now comes the clever(ish) part, which is the third alias:
git svn dcommit && git checkout master && git svn rebase && git stash pop
This commits the contents of the for-commit branch to SVN, then switches back to master and rebases it on top of the latest SVN branch, which now includes the commits just added. Git-SVN is clever enough to realise that some of the commits, but not all, are now in SVN. Gitk looks like this after a final update:
There are a couple of "sidings" in the history. The lower of the two is the old location of the "floating" commits on master. The upper one is the previous location of the for-commit branch. You could tidy them up by restarting gitk, but I find they're useful for keeping track of what I did. If you look closely, you'll notice that someone else added a commit to the Subversion branch between the last time I updated and the final commit, and git-svn dealt with that too.
So there you go. Maybe that's useful to someone.
* I don't actually "right-click", since I'm left-handed. I also use a trackball instead of a mouse. You should get one - they're awesome.