diff -r 60acfbd8f6d6 -r 3107eacaddf7 extra/soundsoftware/create-repo-authormaps.rb
--- a/extra/soundsoftware/create-repo-authormaps.rb
+++ b/extra/soundsoftware/create-repo-authormaps.rb
@@ -18,7 +18,7 @@
 # This script does that, if given the two directory names as arguments
 # to the -s and -o options. In the above example:
 #
-# script/rails runner create-repo-authormaps.rb -s /var/hg -o /var/repo-export/authormap
+# ./script/rails runner -e production extra/soundsoftware/create-repo-authormaps.rb -s /var/hg -o /var/repo-export/authormap
 #
 # Note that this script will overwrite any existing authormap
 # files. (That's why the output files are given an authormap_ prefix,
@@ -28,9 +28,9 @@
 require 'getoptlong'
 
 opts = GetoptLong.new(
-                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
-                      ['--out-dir',      '-o', GetoptLong::REQUIRED_ARGUMENT],
-                      ['--environment',  '-e', GetoptLong::REQUIRED_ARGUMENT]
+                      ['--scm-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--out-dir', '-o', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--environment', '-e', GetoptLong::OPTIONAL_ARGUMENT]
 )
 
 $repos_base   = ''
@@ -74,6 +74,9 @@
 end
 
 projects.each do |proj|
+
+  next unless proj.is_public
+
   next unless proj.respond_to?(:repository)
 
   repo = proj.repository
@@ -90,7 +93,29 @@
 
   authormap = ""
   committers.each do |c, uid|
-    if not c =~ /[^<]+<.*@.*>/ then
+
+    # Some of our repos have broken email addresses in them: e.g. one
+    # changeset has a committer name of the form
+    #
+    # NAME <name <NAME <name@example.com">
+    #
+    # I don't know how it got like that... If the committer has more
+    # than one '<' in it, truncate it just before the first one, and
+    # then look up the author name again.
+    #
+    if c =~ /<.*</ then
+      # So this is a completely pathological case
+      user = User.find_by_id uid
+      if user.nil? then
+        # because the given committer is bogus, we must write something in the map
+        name = c.sub(/\s*<.*$/, "")
+        authormap << "#{c}=#{name} <unknown@example.com>\n"
+      else
+        authormap << "#{c}=#{user.name} <#{user.mail}>\n"
+      end
+    elsif not c =~ /[^<]+<.*@.*>/ then
+      # This is the "normal" case that needs work, where a user has
+      # their name in the commit but no email address
       user = User.find_by_id uid
       authormap << "#{c}=#{user.name} <#{user.mail}>\n" unless user.nil?
     end
diff -r 60acfbd8f6d6 -r 3107eacaddf7 extra/soundsoftware/export-git.sh
--- /dev/null
+++ b/extra/soundsoftware/export-git.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+
+set -e
+
+progdir=$(dirname $0)
+case "$progdir" in
+    /*) ;;
+    *) progdir="$(pwd)/$progdir" ;;
+esac
+
+rails_scriptdir="$progdir/../../script"
+rails="$rails_scriptdir/rails"
+
+if [ ! -x "$rails" ]; then
+    echo "Expected to find rails executable at $rails"
+    exit 2
+fi
+
+fastexport="$progdir/../fast-export/hg-fast-export.sh"
+if [ ! -x "$fastexport" ]; then
+    echo "Expected to find hg-fast-export.sh executable at $fastexport"
+    exit 2
+fi
+
+environment="$1"
+hgdir="$2"
+gitdir="$3"
+
+if [ -z "$hgdir" ] || [ -z "$gitdir" ]; then
+    echo "Usage: $0 <environment> <hgdir> <gitdir>"
+    echo "  where"
+    echo "  - environment is the Rails environment (development or production)"
+    echo "  - hgdir is the directory containing project Mercurial repositories"
+    echo "  - gitdir is the directory in which output git repositories are to be"
+    echo "    created or updated"
+    exit 2
+fi
+
+if [ ! -d "$hgdir" ]; then
+    echo "Mercurial repository directory $hgdir not found"
+    exit 1
+fi
+
+if [ ! -d "$gitdir" ]; then
+    echo "Target git repository dir $gitdir not found (please create at least the empty directory)"
+    exit 1
+fi
+
+set -u
+
+authordir="$gitdir/__AUTHORMAPS"
+mkdir -p "$authordir"
+
+wastedir="$gitdir/__WASTE"
+mkdir -p "$wastedir"
+
+echo
+echo "$0 starting at $(date)"
+
+echo "Extracting author maps..."
+
+# Delete any existing authormap files, because we want to ensure we
+# don't have an authormap for any project that was exportable but has
+# become non-exportable (e.g. has gone private)
+rm -f "$authordir/*"
+
+"$rails" runner -e "$environment" "$progdir/create-repo-authormaps.rb" \
+	 -s "$hgdir" -o "$authordir"
+
+for hgrepo in "$hgdir"/*; do
+
+    if [ ! -d "$hgrepo/.hg" ]; then
+	echo "Directory $hgrepo does not appear to be a Mercurial repo, skipping"
+	continue
+    fi
+
+    reponame=$(basename "$hgrepo")
+    authormap="$authordir/authormap_$reponame"
+
+    git_repodir="$gitdir/$reponame"
+
+    if [ ! -f "$authormap" ]; then
+	echo "No authormap file was created for repo $reponame, skipping"
+
+	# If there is no authormap file, then we should not have a git
+	# mirror -- this is a form of access control, not just an
+	# optimisation (authormap files are expected to exist for all
+	# exportable projects, even if empty). So if a git mirror
+	# exists, we move it away
+	if [ -d "$git_repodir" ]; then
+	    mv "$git_repodir" "$wastedir/$(date +%s).$reponame"
+	fi
+	    
+	continue
+    fi
+    
+    if [ ! -d "$git_repodir" ]; then
+	git init --bare "$git_repodir"
+    fi
+
+    echo
+    echo "About to run fast export for repo $reponame..."
+    
+    (
+	cd "$git_repodir"
+
+        # Force is necessary because git-fast-import (or git) can't handle
+        # branches having more than one head ("Error: repository has at
+        # least one unnamed head"), which happens from time to time in
+        # valid Hg repos. With --force apparently it will just pick one
+        # of the two heads arbitrarily, which is also alarming but is
+        # more likely to be useful 
+	"$fastexport" --quiet -r "$hgrepo" --hgtags -A "$authormap" --force
+
+        git update-server-info
+    )
+
+    echo "Fast export done"
+    
+done
+
+echo "$0 finishing at $(date)"
+
