05 Jan 2009 (permalink)

Mirroring SVN to Git

Puszcza now offers a one-way mirror of its SVN repositories to Git. This article explains the techniques used to implement it.

The basic prerequisites are:

Creating the Mirror

The initial mirror is created using git-svn:

  git svn clone -A$AUTHORS -s $SVNROOT/$PROJECT $GITROOT/$PROJECT

It is supposed that $PROJECT is the project name, and $AUTHORS contains full pathname of the authors file.

The -s switch tells git-svn to assume the standard layout of the source SVN repository (i.e. trunk/, tags/, branches/).

When this command finishes, $GITROOT/$PROJECT will contain a valid Git repository set up for two-way intercommunication with the SVN repository $SVNROOT/$PROJECT. Any existing SVN tags and branches are mapped to remote Git branches. A tag $SVNROOT/$PROJECT/tags/foo becomes branch tags/foo. A branch $SVNROOT/$PROJECT/branches/bar becomes Git branch bar.

This mapping is OK for mirroring, but is a bit inconvenient, because tags and branches are not visible using the gitweb interface.

To fix this, I provide local tracks for each remote branch:

  cd $GITROOT/$PROJECT
  git branch -r |
   while read tag
   do
     case $tag in
     tags/*) localtag=${tag##tags/}
             git tag $localtag $tag
             ;;
     trunk)  ;;
     *)      git branch --track ${HEADPFX}$tag $tag
             ;;
     esac
   done

Note $HEADPFX in the last git-branch invocation. Theoretically, a local branch can have the same name as the remote one it tracks. However such setup makes git issue lots of spurious diagnostics in the form:

  warning: refname 'release_4_4_patches' is ambiguous.

when doing various Git operations, e.g. git checkout.

It is also possible that such duplicate names could cause other, more serious problems with Git. I have not checked this, so I am not sure. Nevertheless, I prefer to stay on the safe side and to prefix all local branches with some unique string, so they differ from the remote ones. In my config I set HEADPFX=git-.

To finish the initial set up, we also need to install a pre-receive hook, which will forbid pushing to this repository.

I use the following hook:

#! /bin/sh
SVN=$(git config --get svn-remote.svn.url)

cat >&2 <<EOT
---------------------------- WARNING! ----------------------------
This repository is a one-way mirror of the SVN repository at

  $SVN
  
Pushing to this repository is administratively prohibited. 

If you wish to drop SVN and switch to Git, please drop an email to
<root@domain> saying so. Make sure the email is GPG-signed.
---------------------------- WARNING! ----------------------------
EOT

exit 1

The git-svn-mirror-create script does all that. To create a mirror of a project foo one will simply run: git-svn-mirror-create foo.

The created repository is not bare. To provide a usual project.git repository, I place Git mirrors in a subdirectory, and then symlink the $PROJECT/.git subdirectory to $GITROOT/$PROJECT.git.

Maintaining the Mirror

Basically, the following commands will keep the currently checked out branch in sync with the SVN:

  git svn fetch
  git svn rebase -l

However, this will not update other branches, so the same operation needs be repeated for each branch:

  cd $GITROOT/$PROJECT 
  git branch | sed 's/^\*//' | tr -d ' ' | tee $BRANCHLIST |
   while read branch
   do
     git checkout $branch
     git svn fetch
     git svn rebase -l
   done
  git checkout master

The file $BRANCHLIST keeps a temporary list of branches. This list is used by the code below to determine whether any new branches were created in the SVN, and, if so, to mirror them:

  echo "Checking for new branches & tags..."
  git tag > $TAGLIST
  git branch -r |
   while read tag
   do
     case $tag in
     tags/*) localtag=${tag##tags/}
             if ! grep -q "^${localtag}\$" $TAGLIST; then
               echo "Mapping $tag to $localtag"
               git tag $localtag $tag
             fi;;
     trunk)  ;;
     *)      if ! grep -q "^${HEADPFX}${tag}\$" $BRANCHLIST; then
               echo "Tracking $tag as ${HEADPFX}$tag"
               git branch --track ${HEADPFX}$tag $tag
             fi;;
     esac
   done

These operations are performed by the script git-svn-mirror-synch, which takes the project name as its argument. This script should be executed as a cron job.

Unbinding the Mirror

After running SVN and Git in parrallel for some time, the project maintainer will probably decide to drop SVN and to switch to Git entirely.

This is done by git-svn-mirror-finish script. This script does the following:

  1. Removes remote branches;
  2. Renames local branches, by removing the $HEADPFX prefix;
  3. Removes SVN-related sections from .git/config;
  4. Disables the pre-receive hook;

The sysadmin is then supposed to convert the repository to bare format and to remove checked out sources. E.g.:

  cd .svn-mirror
  mv .svn-mirror/$PROJECT/.git $PROJECT.git
  rm -rf .svn-mirror/$PROJECT/
  cd $PROJECT.git
  git config bool core.bare true
16 Jan 2009 (permalink)

Wyslij-po 2.0

As part of the update compain following the release of GNU Mailutils 2.0, the version 2.0 of wyslij-po is released.

24 Jan 2009 (permalink)

New version of gnupload script.

23 Mar 2009 (permalink)

Id est, patres conscripti...

25 Mar 2009 (permalink)

Installing Slackware on Eee PC

16 May 2009 (permalink)

Grot

Grot is a rotation tool for MySQL binary logs. Sources etc. are here.

16 Nov 2009 (permalink)

BackUPS Pro 280 Battery Constant

For the record: the battery constant for BackUPS Pro 280 is BA.

If this model shows incredibly low runtime on a new, 100% loaded battery and the state persists even after calibration, try resetting the constant to this value. To do so, follow these instructions.