diff --git a/contrib/mirrorconf-helper.sh b/contrib/mirrorconf-helper.sh
new file mode 100755
index 0000000..a1e36b1
--- /dev/null
+++ b/contrib/mirrorconf-helper.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+# tool to make adding/editing products easier
+# see subconf-example.mkd or html version somewhere
+
+# PRE-REQUISITES
+# 1. gitolite installed on all servers
+# 2. the gitolite-admin repo is also mirrored
+
+# run this program ONLY in a clone of a gitolite-admin repo in a committed
+# state. This way a "git diff" will tell you what changed, and a "git status"
+# will tell you what new files were created, and you can rollback if needed.
+
+usage() {
+ cd $od
+ echo commands:
+ grep '^#.*$0' $0 | cut -c7-
+ echo
+ echo '(please read the inline documentation for more info)'
+}
+
+# ------------------------------------------------------------------------------
+
+# COMMANDS
+
+# ------------------------------------------------------------------------------
+# adding a new host:
+
+# $0 newhost hostname admin-username
+
+# NOTE: this requires you to first add the newhost to the gitolite.conf file
+# in the list of slaves for the admin repo. That is manually done; this
+# script will not do it. You will also have to ensure that the new server
+# being added has been updated and is receiving changes to the admin repo
+# automatically.
+
+# DO NOT PROCEED OTHERWISE. If necessary, check by making a dummy change to
+# the admin repo and pushing, then make sure the new server has received the
+# change.
+
+# ------------------------------------------------------------------------------
+# adding a new product to a master host:
+
+# $0 newprod hostname product-name
+
+# NOTE: the host admin must first create and propagate the
+# master/host/prod.conf file (see section 3, "host admins only").
+
+# ------------------------------------------------------------------------------
+# adding a new slave to a master/prod combo
+
+# $0 newslave master-hostname product-name slave-hostname
+
+# ------------------------------------------------------------------------------
+
+# new *server*: edit gitolite.conf manually (slaves list for the admin repo)
+
+# everything else is done by this tool
+
+# ASSUMPTIONS: we are in a gitolite-admin clone somewhere
+
+die() { echo "$@" >&2; usage; exit 1; }
+finish() { echo >&2; exit 0; }
+
+# go to the conf directory
+od=$PWD; export od
+git rev-parse --show-toplevel >/dev/null || die not in a git directory?
+cd $(git rev-parse --show-toplevel)
+cd conf || die cant find a conf/ subdirectory
+[ -f gitolite.conf ] || die cant find a gitolite.conf file
+
+verify_host() {
+ grep config.*gitolite.mirror gitolite.conf |
+ perl -pe 's/"/ " /g' |
+ grep " $2 " >/dev/null || die "$2 not found in gitolite.conf mirror config"
+}
+
+update_file() {
+ echo >&2
+ echo >&2 ==== appending lines to $1 ====
+ tee -a $1
+}
+
+# ------------------------------------------------------------------------------
+# adding a new host:
+# newhost hostname admin-username
+
+[ "$1" == "newhost" ] && {
+ [ -z "$2" ] && die "need hostname"
+ verify_host master $2
+ [ -f master/$2.conf ] && die "master/$2.conf already exists"
+ [ -z "$3" ] && die "need admin username for host $2"
+
+ (
+ echo
+ printf "@$2\t= $2/..*\n" | expand -32
+ ) | update_file host-product-map.conf
+
+ # setup the first line of the NAME-restrictions.conf file
+ [ -f NAME-restrictions.conf ] || echo "repo gitolite-admin" > NAME-restrictions.conf
+ (
+ echo
+ printf "RW\t= $3\n" | expand -40
+ printf "RW NAME/conf/master/$2/\t= $3\n" | expand -40
+ ) | update_file NAME-restrictions.conf
+
+ mkdir -p master
+ (
+ echo
+ echo "include \"master/$2/*.conf\""
+ ) | update_file master/$2.conf
+
+ finish
+}
+
+# ------------------------------------------------------------------------------
+# adding a new product to a master host:
+# newprod hostname product-name
+
+[ "$1" == "newprod" ] && {
+ [ -z "$2" ] && die "need hostname"
+ verify_host master $2
+ [ -f master/$2.conf ] || die "host $2 not found; forgot to run 'newhost'?"
+ [ -z "$3" ] && die "need product name to add"
+ [ -f master/$2/$3.conf ] || die "master/$2/$3.conf not found; contact host-admin for $2"
+ [ -f mirrors/$2/$3.conf ] && die "mirrors/$2/$3.conf already exists"
+
+ (
+ echo
+ printf "@$2\t= $3/..*\n" | expand -32
+ ) | update_file host-product-map.conf
+
+ finish
+}
+
+# ------------------------------------------------------------------------------
+# adding a new slave to a master/prod combo
+# newslave master-hostname product-name slave-hostname
+
+[ "$1" == "newslave" ] && {
+ [ -z "$2" ] && die "need hostname"
+ verify_host master $2
+ [ -f master/$2.conf ] || die "host $2 not found; forgot to run 'newhost'?"
+ [ -z "$3" ] && die "need product name to add"
+ [ -f master/$2/$3.conf ] || die "master/$2/$3.conf not found; contact host-admin for $2"
+ [ -z "$4" ] && die "need slave name to add"
+ verify_host slave $4
+
+ # first create lines in slave/slavename/mastername.conf
+ f="slave/$4/$2.conf"
+ i="$2/$3.conf"
+ [ -f $f ] && grep "$i" "$f" >/dev/null && die "$f already contains lines for $i"
+
+ mkdir -p slave/$4
+ (
+ echo
+ echo "include \"master/$i\""
+ echo "include \"mirrors/$i\""
+ ) | update_file "$f"
+
+ # now check how many slaves we have for this and overwrite mirrors/$2/$3.conf
+ sl=$(echo slave/*/$2.conf | perl -pe "chomp; s(slave/(.*?)/$2.conf)(\$1)g")
+ f="mirrors/$2/$3.conf"
+
+ [ -f $f ] && {
+ echo >&2
+ echo >&2 "==== overwriting file $f; old contents:"
+ cat >&2 $f
+ > $f
+ }
+
+ mkdir -p mirrors/$2
+ (
+ echo "repo $3/..*"
+ echo " config gitolite.mirror.master = \"$2\""
+ echo " config gitolite.mirror.slaves = \"$sl\""
+ ) | update_file $f
+
+ finish
+}
+
+usage
diff --git a/contrib/mirroring-complex-example.mkd b/contrib/mirroring-complex-example.mkd
new file mode 100644
index 0000000..0cc8117
--- /dev/null
+++ b/contrib/mirroring-complex-example.mkd
@@ -0,0 +1,344 @@
+# semi-autonomous mirroring setup example
+
+[deldoc]: http://sitaramc.github.com/gitolite/doc/delegation.html
+[sc]: http://sitaramc.github.com/gitolite/doc/delegation.html#_the_subconf_command
+
+This document describes one way to do this. Gitolite is powerful so you can
+probably find other ways to suit you.
+
+In this document:
+
+ * overview of problem
+ * overview of setup
+ * gitolite feature recap
+ * pre-requisites
+ * quick setup
+ * step by step
+ * (1) `gitolite.conf`
+ * (2) `master/sam.conf`
+ * (3) host admins only -- `master/sam/p1.conf`
+ * (4) `mirrors/sam/p1.conf`
+ * (5) `slave/frodo/sam.conf`
+ * (6) manual sync
+ * next steps
+ * appendix A: delegation helper files
+
+----
+
+
+
+### overview of problem
+
+The example is from real life, with the following characteristics:
+
+ * multiple servers (hosts)
+ * multiple "products", each product has one or more git repos
+ * different products are "native to" (mastered on) different hosts
+ * a product may be mirrored to zero or more other hosts (mirrored to zero
+ hosts means it is **local** to the host)
+
+The admin requirements are:
+
+ * the overall system will have one or more **master admins**; they manage
+ the main config file, for instance.
+ * each host will have one or more **host admins**
+ * these host admins should be allowed to create any repos they need (within
+ one of a set of directory names allocated to them), and assign access to
+ whomever they please
+ * they should not be able to "step on each other" -- setup access rules for
+ repos not in their control
+
+The following can only be done by the master admins:
+
+ * authentication (ssh keys) are centrally managed and distributed. Host
+ admins should not be allowed to do this.
+ * mirroring setup -- who's the master and who're the slaves for any repo
+ * allowing redirected pushes from slaves
+
+
+
+### overview of setup
+
+We will use p1 as the product, with sam as the master and frodo as a slave.
+Assume equivalent text/code for other product/master/slave combos.
+
+This setup imposes the condition that all repos should be under some directory
+name; either a product name or, for local repos, a hostname. In our example,
+these directory names would be p1 or sam on the host sam, and frodo on the
+host frodo.
+
+
+
+#### gitolite feature recap
+
+We use [delegation][deldoc], to ensure that admins for sam can only write
+files whose names start with `master/sam/`. The actual files they will write
+are `master/sam/p1.conf` etc., one for each product that is mastered on their
+server.
+
+We use [subconf][sc]. When you say `subconf "path/to/foo.conf`, then within
+that file (and anything included from it), access can only be defined for
+repos that regex-match one of the elements of `@foo`.
+
+
+
+### pre-requisites
+
+First, install mirroring on all servers according to the main mirroring
+document. Set it up so that the gitolite-admin repo is mastered at one server
+and everyone else slaves it.
+
+Also, after (or during) the normal mirroring install, edit `~/.gitolite.rc` on
+all servers and set `$GL_WILDREPOS` to 1 (from its default of 0).
+
+
+
+### quick setup
+
+ * edit your `gitolite.conf` file as given in step 1 below
+ * ignore all the comments, even the ones that tell you to do something :-)
+ * change only the names of the admin users, and the names of the servers
+ in the config lines. Everything else stays the same
+ * now copy `mirror-conf-helper` from the contrib directory to your home or
+ some easy to type place, and run it to setup your hosts, products, and slaves
+
+A typical sequence with that script is:
+
+ # cd to your gitolite-admin clone
+
+ # create a new host
+ ~/mirror-conf-helper newhost sam sam-admin
+
+ # create the actual repository and access rules. This is done by the host
+ # admin for the host on which it is mastered (or you can do it yourself).
+ # See step 3 below for details.
+ mkdir -p conf/master/sam
+ vim conf/master/sam/p1.conf
+ # now add in some "repo p1/..." and "RW ..." lines for the repos in
+ # product p1 and save
+
+ # add product p1 to the list of repos sam is allowed to control
+ ~/mirror-conf-helper newprod sam p1
+
+ # add a slave
+ ~/mirror-conf-helper newslave sam p1 frodo
+
+You can then treat the detailed steps described below as extra information or
+"background reading" ;-)
+
+
+
+### step by step
+
+If the script is not cutting it for you and want to vary the technique for
+some reason, or you simply want to gain a better understanding of what is
+happening, it may be better to do each step manually instead of just using the
+script.
+
+**Note**: all files mentioned below are assumed to be under **`conf/`**. The
+only place where you have to explicitly state this is in the delegation code
+in the appendix. The rest of the time, "conf/" is assumed.
+
+
+
+#### (1) `gitolite.conf`
+
+The main config file has these items in it. **Please add them in this
+order**.
+
+If you follow this document completely, your gitolite.conf file can be pretty
+static, changing only if the master admin changes or you need to add a new
+host as slave to the gitolite-admin repo. Therefore you can set it up first.
+
+Here's what it looks like:
+
+ # (1.1)---------------------------------------------------------------------
+ # First the main setup:
+
+ @master-admins = sitaram dilbert
+ repo gitolite-admin
+ RW+ = @master-admins
+ config gitolite.mirror.master = "master"
+ config gitolite.mirror.slaves = "list of slave servers"
+ # you cannot use continuation lines for this; sorry! You have to list
+ # them all in ONE long line within one set of double quotes...
+
+ # (1.2)---------------------------------------------------------------------
+ # If you have any files with "convenience" group definitions, pull them in:
+
+ include "groups/users.conf"
+ include "groups/repos.conf"
+
+ # (1.3)---------------------------------------------------------------------
+ # Next is delegation. If you don't want delegation, omit this section,
+ # and replace all "subconf" commands with "include" in the rest of this
+ # document.
+
+ include "host-product-map.conf"
+ # create this file; see step A1 in appendix A
+
+ repo gitolite-admin
+ # now that you're adding a NAME/ section, you need this for master
+ # admins to retain their access
+ RW+ NAME/ = @master-admins
+
+ include "NAME-restrictions.conf"
+ # create this file; see step A2 in appendix A
+
+ # (1.4)---------------------------------------------------------------------
+ # Now you include the access rules for native repos
+ # (example: master/sam.conf)
+
+ subconf "master/HOSTNAME.conf"
+
+ # (1.5)---------------------------------------------------------------------
+ # After this you have the mirror config for native repos. We place this
+ # *after* the access rules above, to make sure we override any mirror
+ # config lines accidentally added by a host admin!
+ # (example: mirrors/sam/p1.conf)
+
+ include "mirrors/HOSTNAME/*.conf"
+
+ # (1.6)---------------------------------------------------------------------
+ # Now we pull in all setup (mirror config *and* access rules) for
+ # non-native repos. For the product "p1", this file will get pulled in
+ # when the config is processed on frodo.
+ # (example: slave/frodo/sam.conf)
+
+ subconf "slave/HOSTNAME/*.conf"
+
+You'll get some warnings about missing include files; ignore them.
+
+
+
+#### (2) `master/sam.conf`
+
+For each host sam, one file called `master/sam.conf` is needed. This file
+contains just one line:
+
+ include "master/sam/*.conf"
+
+It is pulled in by the main config file using `subconf
+"master/HOSTNAME.conf"`, which -- on host sam -- translates to `subconf
+"master/sam.conf"`. The only purpose of this is to setup the subconf
+restriction on the combined contents of `master/sam/*.conf`.
+
+
+
+#### (3) host admins only -- `master/sam/p1.conf`
+
+(recap: the host admins for sam can only write files in `master/sam`).
+
+For each product p1 with master on host sam, the admins for host sam will
+create a file `master/sam/p1.conf`. This file will contain reponames (must
+start with `p1/`) and access rules for these repos.
+
+If they have some common groupnames etc., they can probably put them in
+`master/sam/users.conf` or some such file and pull those in into each of their
+product.conf files.
+
+By default, everything is local to their server. (Mirroring can only be setup
+by the master admins).
+
+
+
+#### (4) `mirrors/sam/p1.conf`
+
+For each product p1 mastered on host sam, a file called `mirrors/sam/p1.conf`
+will be created, containing mirror config lines for all repos of product p1.
+In this case, it could be
+
+ repo p1/..*
+ config gitolite.mirror.master = "sam"
+ config gitolite.mirror.slaves = "frodo"
+
+If this file does not exist, p1 is local to sam and not mirrored.
+
+
+
+#### (5) `slave/frodo/sam.conf`
+
+For each product that slave frodo gets from master sam, this file has the
+following lines
+
+ # pull in the access lines
+ include "master/sam/p1.conf"
+
+ # pull in the mirror config lines
+ include "mirrors/sam/p1.conf"
+
+This file is pulled in on a slave server via a `subconf
+slave/HOSTNAME/*.conf` line in the main config file. On frodo, this would
+pull in `slave/frodo/sam.conf` (among others), establishing, again, a subconf
+restriction on `@sam`.
+
+Security note: what this achieves is that the access lines,
+which were written by sam's admins, are parsed on frodo *under the subconf
+restriction of "sam"*. This is important to prevent sam's admins from writing
+rules for repos they don't own and having them processed on other
+servers!
+
+
+
+#### (6) manual sync
+
+The new repo(s) you just created would not have been synced up to frodo. You
+can either make an empty commit and push, or log on to sam and run
+
+ gl-mirror-shell request-push p1/reponame
+
+
+
+### next steps
+
+Once you've done the initial setup, here's what ongoing additions will
+require.
+
+ * any new repos that are created for the same *product* require only step 3
+
+ * a new *product* will require steps A1, 3, 4 and 5
+
+ * a new *host* will require additions in all the steps, including adding the
+ hostname in the slaves list for the admin repo (this is in the main
+ gitolite.conf file)
+
+
+
+### appendix A: delegation helper files
+
+These two files were briefly mentioned in the delegation setup.
+
+(A1) `conf/host-product-map.conf` has the following contents:
+
+ # For each host foo, there will be one line:
+ # @foo = foo/..*
+ # line (for local repos for the host).
+
+ # For each product bar whose master is a host foo, there will be one line:
+ # @foo = bar/..*
+ # to add bar/..* to the allowed patterns when subconf "foo" is in effect
+
+ # ------------------------------------------------------------------------------
+ # so for our example:
+
+ @sam = sam/..*
+ @sam = p1/..*
+
+ @frodo = frodo/..*
+
+(A2) `conf/NAME-restrictions.conf` has the following contents:
+
+ # For each host foo, there will be two lines:
+ # RW = username-of-foo-host-admin
+ # RW NAME/conf/master/foo/ = username-of-foo-host-admin
+ # IMPORTANT: DO NOT MISS THE TRAILING SLASH IN THE LINE ABOVE!
+
+ # ------------------------------------------------------------------------------
+
+ repo gitolite-admin # this line is required
+
+ RW = sam-admin
+ RW NAME/conf/master/sam/ = sam-admin
+
+ RW = frodo-admin
+ RW NAME/conf/master/frodo/ = frodo-admin