Easy git-svn for Rails (or "git-me-up")

I've been trying to persuade git-svn to work properly with Rails plugins that are installed via svn:externals. Whilst working out how to do it I stumbled across several great articles, but I couldn't get any of the solutions presented to work perfectly. Samuel Tesla's article is especially informative, but for a long time I couldn't stop git-svn from trying to commit Git metadata back into my Subversion repository...

After a bit of head scratching I got it sorted, and wrapped the process up in a little utility called git-me-up.

What does git-me-up do?

In a nutshell, it:

  1. Clones the latest version of the source in your Subversion repository into a new local Git repository. It doesn't import any history for performance reasons.

  2. Recreates empty directories that git-svn ignored (but that your app might need).

  3. Converts any svn:ignore Subversion properties into entries in the .git/info/exclude file.

  4. Checks for Rails plugins installed via svn:externals and clones them into their own git repositories. Each external plugin is then symlinked into vendor/plugins.

  5. Creates a local working branch.

In other words, it takes all the hard work out of creating a local Git repository that points at your Subversion repo.

Installation

You need git-svn installed first. On a Mac, install MacPorts and then type:

$ sudo port install -u git-core +svn +bash_completion

When the package has finished building run these commands to benefit from bash completion:

$ cp /opt/local/etc/bash_completion.d/git ~/.git-bash-completion.sh
$ echo '[ -f ~/.git-bash-completion.sh ] && . ~/.git-bash-completion.sh' \
    >> ~/.bashrc
$ . ~/.bashrc

On Ubuntu Linux:

$ sudo apt-get install git-svn

Installing git-me-up

The source is hosted on GitHub. You can either download the tar ball or pull the source with git:

$ git clone git://github.com/gma/git-me-up.git

The Makefile will install it into /usr/local/bin:

$ cd git-me-up
$ sudo make install

Using git-me-up

This is pretty easy. Let's start with an empty directory so you can see what gets created:

$ pwd
/home/graham/test
$ git-me-up https://myhost.com/path/to/myproject/trunk myproject
git-me-up: finding latest revision of https://myhost.com/path/to/myproject/trunk
git-me-up: creating git repository in /home/graham/test/myproject
git-me-up: making empty directory: app/views/comments
git-me-up: making empty directory: app/views/pages
git-me-up: making empty directory: components
git-me-up: making empty directory: log
git-me-up: making empty directory: test/mocks/development
git-me-up: making empty directory: test/mocks/test
git-me-up: making empty directory: tmp/cache
git-me-up: making empty directory: tmp/pids
git-me-up: making empty directory: tmp/sessions
git-me-up: making empty directory: tmp/sockets
git-me-up: setting ignored files from subversion (this can take a while)
git-me-up: checking for plugins installed with svn:externals
git-me-up: finding latest revision of https://myhost.com/path/to/plugin/trunk
git-me-up: creating git repository in /home/graham/test/myproject/../plugins/site_theme
git-me-up: setting ignored files from subversion (this can take a while)
git-me-up: symlinking site_theme into myproject/vendor/plugins
git-me-up: creating 'work' branch on /home/graham/test/myproject
Switched to a new branch "work"
$ ls
myproject/  plugins/

If you're running on a Mac you may see this error message part way through the output:

No such file or directory: PROPFIND request failed on '/app': Could not open the requested SVN filesystem at /opt/local/bin/git-svn line 1872

It appears as though the git svn show-ignore command (using the current version of git from MacPorts--version 1.5.4.4) doesn't work on my Mac. If you see this too don't worry about it; it will only mean that Git doesn't know which files you've chosen to ignore in your Subversion repo.

Now we're ready to use our new Git repo. Note that we've got a work branch by default:

$ cd myproject
$ git branch
  master
* work

If you want to call the branch something else, set the BRANCH environment variable before you run git-me-up.

Samuel recommends a good work flow for working with Subversion (skip down to "Step Five: Using this thing"). When you want to update your working branch from subversion, try this:

$ git checkout master  # switches to local master branch
$ git svn rebase       # pulls recent changes from subversion
$ git checkout work    # switches to local work branch
$ git rebase master    # pulls recent changes to master into work

There's a good description of why rebase is so great on James Bowes's blog.

When you want to commit changes back into subversion:

$ git checkout work    # you were probably there already
... run your tests ...
$ git commit -a -m "Blah blah blah..."
$ git checkout master
$ git merge work
$ git svn rebase       # checks for fresh changes in subversion
... run tests again if rebase pulled any changes ...
$ git svn dcommit

Make sure that you use git svn rebase to pull upstream changes into your master branch before you check in.

What git-me-up does with svn:externals plugins

The Rails app that I cloned above has a plugin that is installed via svn:externals called site_theme. I usually use piston or braid to install Rails plugins, but when the plugin is stored on my own servers I've always preferred to pull in the source directly by setting the svn:externals property.

I initially tried to convert svn:externals into Git submodules, but it didn't work well. No matter what I tried, I couldn't persuade git-svn to ignore the submodule when committing back to Subversion. It turns out that it's easy to achieve with a symbolic link.

git-me-up cloned the site-theme plugin into a Git repository, then made it available with a symbolic link:

$ ls
myproject/  plugins/
$ ls plugins/
site_theme/
$ ls -l myproject/vendor/plugins/ | grep site_theme
lrwxrwxrwx  1 graham development   32 Apr 24 18:41 site_theme -> ../../../plugins/site_theme/

To avoid checking the symlink into Subversion, git-me-up told Git to ignore it:

$ grep site_theme myproject/.git/info/exclude
/vendor/plugins/site_theme

The result is a trouble free alternative to submodules that allows you to integrate Git with svn:externals in your Subversion repo.

I love feedback and questions -- please say hello on Twitter or leave a comment.