From Subversion to Git

2011-08-02

Origin

You administer a subversion repository that is a hub of collaboration for developers, and you fancy git. You dream of a better tomorrow.

Destination

You administer a subversion repository that automatically pushes all commits to, and therefore holds a subset of, a git-svn repository and, from there, to gitolite. Developers collaborate via either repository, and changes made through git are pushed to subversion using git-svn. Your dreams are well on their way to coming true.

Preparation

You have shell access to your code server, where git, git-svn, and gitolite are already installed. You have a fair bit of storage space available to hold all the git-svn reference maps.

Journey

SVN_PATH
a local file:// path to your subversion repository.
SVN_URI
the canonical http:, ssh: or svn: URI to your subversion repository.
GITSVN_PATH
a local path to an empty directory that is not shared.

Create and initialize a git repository that is going to receive changes directly from subversion:

$ cd GITSVN_PATH
$ git init
$ git svn init --stdlayout --rewrite-root=SVN_URI SVN_PATH

Create a new text file at GITSVN_PATH/.git/authors.txt containing one line per user, similar to this:

svn_user_name = Full Name <email@address>
joshua = Joshua Tacoma <me@joshuatacoma.ca>

Make sure the authors file will always be used by adding this setting to GITSVN_PATH/.git/config:

[svn]
    authorsfile = .git/authors.txt

Finally, you are ready to give the computer some heavy work! Unfortunately (in my experience) this can’t all be done with one simple git svn fetch:

$ git svn fetch --revision 1:1000 &&
> git svn fetch --revision 1000:2000 &&
> git svn fetch --revision 2000:3000

You may notice this repository is pretty darned big, so now is a good time to mention:

Beware the git-gc command’s --aggressive option! (Don’t use it!)

GITOLITE_NAME
the name of the new repository in gitolite.
GITOLITE_URI
the public URI of the new repository e.g. gitolite@code:GITOLITE_NAME.git

Configure a new gitolite repository and make sure that a subversion post-commit hook will be able to push changes into it. Your gitolite.conf file should include lines like this:

@subversion       = the accounts that process svn commit hooks
@devs             = yourself and all the other developers

repo GITOLITE_NAME
    RW           = @subversion
    RW+ develop/ = @devs
    RW+ feature/ = @devs
    RW+ release/ = @devs
    RW+ hotfix/  = @devs
    RW+ support/ = @devs
    R            = @all</code></pre>

Add GITOLITE_URI as a remote in the git-svn repository:

$ git remote add origin GITOLITE_URI

Change GITSVN_PATH/.git/config so that the [remote "origin"] section looks exactly as follows, replacing GITOLITE_URI:

[remote "origin"]
    url = GITOLITE_URI
fetch = +refs/remotes/*:refs/remotes/origin/*
push = refs/remotes/*:refs/heads/*</code></pre>

The following commands, executed within GITSVN_PATH will fetch the latest commits from subversion and then push everything to gitolite:

$ git svn fetch &&
> git push origin

Create/modify SVN_PATH/hooks/post-commit. Here is a perfectly functional example:

#!/bin/bash
cd GITSVN_PATH && git svn fetch --quiet && git push --quiet origin</code></pre>

If the post-commit command produces any output whatsoever, subversion clients will be confused about whether the commit succeeded, resulting in false conflicts!

You now administer a subversion repository that automatically pushes all commits to, and therefore holds a subset of, a git-svn repository and, from there, to gitolite. Developers may collaborate via either repository, but changes made through git must be pushed to subversion manually using git-svn. Your dreams are well on their way to coming true!

Legacy

Some of the work here, especially the GITSVN_PATH/.git/config section, is based on Thomas Ferris Nicolaisen’s excellent Git-SVN Mirror for multiple branches.