The Git-SVN interface

Subversion (SVN) is a well established version control system, used for many open source and scientific projects. It is a centralised VCS, meaning it has a number of disadvantages compared to git, in particular:

  • All actions (status, diff, commit) are performed over the network, so they are quite slow, and can only be performed online.
  • Branches and tags all on the server: there are no private branches. Partially finished work is hard to deal with.
  • Missing some cool tools like git word-diff and git blame.

There is no real reason to use Subversion for your own projects: git does everything that Subversion does, but faster, and while using less bandwidth and less disk space. For these reasons, git is quickly becoming more popular than subversion. But many older projects are still using subversion, so you may have to too.

Git has a neat interface that downloads a copy of an SVN repository data, and creates a git repository as a front-end. You can then interact as normal with the git repository, using the git tools, commiting and branching as neccesary. When you are ready, you can upload your changes to the SVN repository.

Equivalent commands

Git was designed with SVN in mind, on the basis when in doubt, do the opposite of SVN. Unfortunately, this means there are some confusing terminology differences, that anyone familiar with SVN would have already noticed. The basic commands to use git as an svn client follow:

SVN Git-SVN
svn checkout <url> git svn clone <url>
svn update git svn rebase
svn commit
git add <files>
git commit
git svn dcommit

When git clones the subversion repository, it actually creates a full git repository, with the full history of the Subversion respository at the url given, and with some extra meta-data for tracking the SVN repository. So you can now use all the normal git commands, such as git log, git diff, etc. as normal.

Branching

SVN’s history is basically linear tracking of changes to files. Branches are simply copies of a directory (i.e. trunk), with the complete history of the files in that directory. In contrast, git tracks content, and branches are just tags that move forward as you make new commits, retain the history of each ancestral commit. Git’s branches can get quite complex, but because git only cares about content, and not files, it can easily take care of very complex branch histories.

In practice, this means that when you’re using git as a front-end to Subversion, you have to be a little careful when using git’s branches. You can make as many branches as you like in git, but when you go to merge them back into SVN, you need to make sure that your changes appear linearly from the most recent git svn rebase.

For simple changes, you can just use the basic workflow:

git svn clone -r HEAD https://example.com/svn-repo/trunk     # -r HEAD only downloads the latest revision of trunk, with no history
...
change files
...
git add <files>
git commit -m "commit message"
git svn dcommit

For more complex work, you can create your own local git branches, as normal:

git branch new_feature
...
add and commit changes
...
more commits
...

That might leave you with a history like this:

        D--E--F (new_feature)
       /
A--B--C (master)

Which would be fine, but then we do a git svn fetch, and see that someone else has made an SVN commit in the meantime, like this:

        D--E--F (new_feature)
       /
A--B--C--G (master)

If this was git, there would be no problem; we could just git merge master to merge the new changes into our branch, and keep working, which would result in:

        D--E--F--H  (new_feature)
       /        /
A--B--C--G------ (master)

And we could later merge that feature back into master, and retain the whole history.

It might be possible to handle something as simple this with Subversion, but with anything more complex, things can get very confusing, as you have to know which changes to merge into which branch. To avoid this problem, we can use git rebase master, which is a bit like a merge, but actually re-writes history, replaying the changes one-by-one onto the new master version. That will result in something like:

           D'--E'--F' (new_feature)
          /
A--B--C--G (master)

D’ now includes the changes made in G, and replays the changes made to C onto the new base. We now have a linear history, that will play nicely with SVN. We can merge the branch into master and commit to SVN by simply doing:

$ git checkout master
$ git merge new_feature
$ git svn dcommit

Which will result in the simple history:

A--B--C--G--D'--E'--F' (master, new_feature)

The new_feature branch is a local git-only branch, so we can just delete it, knowing that the changes made on it are now included in the SVN trunk, or we can leave it there for future reference.

Migrating to git

Git-SVN isn’t an excellent solution, as it is very limited by Subversion’s limited branching and merging. Forunately, conversion of Subversion repositories to git repositories is actually very simple – we’ve already done it above, for a single branch. A complete conversion just requires a full git svn clone of the SVN repository, with a couple of extra options to improve the authorship on commits. You then have a full git version of the repository, and you can add a new remote on bitbucket or github, git push, and start sharing immediately.