As a developer nowadays using a source code management system is non-optional. I’ve been a happy user of cvs for quite a while now, as it is complex enough for all my use cases and simple enough to allow fixing things with a text editor without breaking other people’s checkouts. I’ve had little reason to change this, as cvs was available everywhere and with ezjail – one of my more important projects – it was even essential providing means to checkout its latest development state on a vanilla FreeBSD installation, where cvs was the only scm system provided.

However, time moves on. The FreeBSD project chose to remove cvs from base system in its next major release 1 and OSX Developer Tools ship without cvs from OSX 10.8 onward. So it was time for me to move on, as well. The choice to migrate FreeBSD development to subversion 2 seemed not such a bad idea back in 2008, but for me svn has always been a world of pain. It adds complexity without providing any benefit and removed the option for simple repository manipulation when things went awry. In 2013 the only sane option – despite a creeping headache considering the license – is git. Its increased complexity pays off by having integrity checks, a well established user base, an almost fanatical devotion to the pope and in the end I can use it as I used cvs.

I set up gitolite 3 with a UMASK of 0022 4 (to save me trouble later with tools like cgit and gitdaemon) and created empty repositories for each project to migrate. After playing around with several tools, I found cvs2git 5 the best option, allowing me to import the cvs repositories onsite with this tiny script:

git clone${project}
mkdir -p dumps/
cvs2git --blobfile=dumps/${project}-git-blob.dat --dumpfile=dumps/${project}-git-dump.dat --username=cvs2git --fallback-encoding=utf8 ${CVSROOT}/${project}
# Use a text editor the fix committer’s emails, etc here in the dumps/${project}-git-dump.dat file
cd ${project}
cat ../dumps/${project}-git-blob.dat ../dumps/${project}-git-dump.dat | git fast-import
git checkout master
git gc --prune=now
git push origin master
cd ..

This scripts needs to be run as a user who can read CVSROOT and has commit rights to the gitolite repositories.

Being the polite hacker that I am, I wanted to avoid breaking other people's checkouts with my migration. I also need to provide backward compatibility to users of FreeBSD installations that still come with cvs only. This means that the pserver URIs need to remain intact. However, the tool I hoped would solve this problem – git-cvsserver 6 – comes with some surprising mapping of cvs modules into git branches. Which basically renders it unusable as a legacy support mechanism. This left me with little choice but keeping the old cvs repositories as write-only copies. I wrote a git commit hook that commits every change 7 to cvs using a dummy checkout in /home/cvs/${project}, after granting the git user commit rights to cvs. This works well, the only drawback is that it makes all commits appear to come from git in the cvs view. But I think this is an acceptable price.

In order to provide an additional update commit hook and not break gitolite’s builtin hook, I needed to add a so-called VREF 8 to the repo config, which looks like this in my conf/gitolite.conf:

repo ezjail opentracker minimunin jaildaemon
    RW+              = id_dsa
    R                = @all
    -   VREF/cvspush = @all

My git repos reside in /usr/local/git/, so I put my commit hook script to /usr/local/git/.gitolite/local/VREF/cvspush and fixed my /usr/local/git/.gitolite.rc to have an entry:

LOCAL_CODE => "$ENV{HOME}/.gitolite/local",

The hook itself is here (don’t forget to set +x permissions. Also if you checkout your cvs repositories somewhere other than /home/cvs, you need to change this, as well):

# ignore changes not for master branch
[ "$1" = "refs/heads/master" ][] || exit 0
# see if we have a legacy CVS repository to commit to
[ -d "/home/cvs/${GL_REPO}/CVS/" ][] || exit 0
export GIT_DIR="${GL_REPO_BASE}/${GL_REPO}.git"
cd "/home/cvs/${GL_REPO}/" || exit 0
# get all the commits leading up to that push
for commit in `git rev-list "$2".."$3"`; do
  git cvsexportcommit -k -u -c -v ${commit}

And finally all my project description pages were updated to reflect the new way to checkout the source code, as was the web interface 9. All thats left now is to provide read only svn access to the projects, for all FreeBSD users running 10.